Skip to content

[ffigen] Cpp unique ptr ownership - #3513

Open
Hassnaa9 wants to merge 3 commits into
dart-lang:mainfrom
Hassnaa9:cpp-unique-ptr-ownership
Open

[ffigen] Cpp unique ptr ownership#3513
Hassnaa9 wants to merge 3 commits into
dart-lang:mainfrom
Hassnaa9:cpp-unique-ptr-ownership

Conversation

@Hassnaa9

@Hassnaa9 Hassnaa9 commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

No description provided.

@liamappelbe
liamappelbe self-requested a review August 2, 2026 23:59
final numTemplateArgs = clang.clang_Type_getNumTemplateArguments(cxtype);
if (numTemplateArgs >= 1) {
final spelling = clang.clang_getTypeSpelling(cxtype).toStringAndDispose();
if (spelling.contains('unique_ptr<')) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you able to do this check using the USR instead? If you use the type spelling, then a custom type name containing "unique_ptr" would be parsed as a std::unique_ptr. If the USR varies depending on what types the template is instantiated with, maybe you could get the USR of the underlying template definition?

throw StateError('This object has already been disposed.');
}

if (node._ptr == ffi.nullptr) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DRY. This is mostly the same stuff that node.releaseOwnership() does, so you should call that util here rather than duplicating code.

The only extra thing you're doing here is setting node._ptr to null and getting the pointer first. You could consider adding a new util that does those things. Or maybe releaseOwnership should do those things? WDYT?

if (method.isConstructor) {
returnTypeString = '$originalName*';
params = method.parameters.map(paramDecl).join(', ');
final callArgs = method.parameters.map((p) => p.name).join(', ');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A constructor can take a std::unique_ptr too. Can you add a test for this case?


final callArgs = method.parameters
.map((p) {
if (p.type is CppClassPointerType &&

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is complicated enough that it should probably be a separate private util function, for readability.


// Check if any method uses unique_ptr (owned ownership) so we can emit
// #include <memory> conditionally rather than always.
final needsMemoryHeader = methods.any(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no real harm in unconditionally including <memory>. I'd rather keep things simple.

params = ['$selfType* self', ...otherParams].join(', ');
body = '${returnPrefix}self->${method.originalName}($callArgs);';
final isUniquePtrReturn =
method.returnType is CppClassPointerType &&

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DRY. You're using this pattern of checking whether something is a unique_ptr often enough that it should be a util. Code duplication adds a maintenance burden over time.

final m = method.originalName;
body = '${returnPrefix}self->$m($callArgs).release();';
} else {
body =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DRY. These two cases are almost identical. A cleaner approach would be something like:

final methodName = method.originalName;
final suffix = isUniquePtrReturn ? 'release()' : '';
body = '${returnPrefix}self->$methodName($callArgs)$suffix;'

final innerType = getCodeGenType(context, innerCXType);

if (innerType is CppClass) {
final className = innerType.symbol.isFilled

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The symbol is never filled at this stage of the pipeline. Symbol renaming doesn't happen until much later.

}
}

enum OwnershipKind { unowned, owned }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Call this CppOwnershipKind. Also, rename owned to unique_ptr, because it's specific to std::unique_ptr (you're hard coding std::unique_ptr in the C++ glue code). If we add more kinds of smart pointer later, we'll need more options here.

String? objCEnclosingClass,
}) => '${cppClass.name}.fromPointer($value)';
}) {
if (ownership == OwnershipKind.owned) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since so many methods have different behavior based on the OwnershipKind, you should consider making a separate class for this. Eg, CppUniquePointerType. That would also simplify some of the other type checks you do in cpp_class.dart.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants