How can I use the pimpl idiom with std::unique_ptr with Swift/C++ interop?

I created a C++ class that I am accessing from Swift:

class Foo {
public:
    Foo();

private:
    class Implementation;
    std::shared_ptr<Implementation> implementation;
};

That worked fine. But I realized I don't really need a shared pointer, so I changed it to a unique_ptr. Then I got an error about invalid application of sizeof to an incomplete type. After doing some research, I learned that I needed to define a destructor and implement in the source file (where Implementation is a complete type) as = default.

// Foo.h
class Foo {
public:
    Foo();
    ~Foo();

private:
    class Implementation;
    std::unique_ptr<Implementation> implementation;
};

// Foo.cpp
class Implementation {
// ...
};

Foo::~Foo() = default;

The C++ code compiled fine, but now my Swift code can't find Foo and I get Record 'Foo' is not automatically available: does not have a copy constructor or destructor; does this type have reference semantics?

I expected it to be exposed as a ~Copyable type, as per the documentation:

C++ structures and classes with a deleted copy constructor are represented as non-copyable Swift types (~Copyable).

Can anyone give me any suggestions as to what I'm doing wrong?

1 Like

Try annotating the class with SWIFT_NONCOPYABLE and create an empty copy constructor that does nothing

Doesn't "deleted copy constructor" in C++ mean explicitly? As in,

    Foo(const Foo&) = delete;

? And just for C++ cleanliness, I'd probably think about also deleting the assignment operator?

Since C++11, the default copy constructor is considered “defined as deleted” in situations where the previous standards would have considered it “undefined”: Copy constructors - cppreference.com

1 Like

Sorry it took me a while to get back to this.

I tried this approach, but SWIFT_NONCOPYABLE doesn't seem to work. When I perform an assignment, it calls the empty copy constructor I created. I think it should consume the source instead of copying, right?

Also, if this approach did work, it seems less than ideal, since it makes the copy constructor that should never be called available on the C++ side.

This is what I have now:

// Foo.h
#include <swift/bridging>

class SWIFT_NONCOPYABLE Foo {
public:
    Foo();
    Foo(const Foo&);
    ~Foo();

private:
    class Implementation;
    std::unique_ptr<Implementation> implementation;
};

// Foo.cpp
class Implementation {
// ...
};

Foo::Foo(const Foo& other) {}
Foo::~Foo() = default;
// UseFoo.swift
let foo = Foo();
let fooCopy = foo; // Calls the copy constructor