c++objective-cobjective-c++

Will ObjC setter automatically copy a C++ object passed as a parameter when get called?


I recently read about a std::unique_ptr as a @property in objective c and the suggestion to store a unique_ptr in ObjC as a property is as following:

-(void) setPtr:(std::unique_ptr<MyClass>)ptr {
    _ptr = std::move(ptr);
}

My question is in ObjC, does the parameter get copied in this case? Because if that happens, unique_ptr shall never be declared as a property right?


Solution

  • My question is in ObjC, does the parameter get copied in this case?

    That depends. Let me introduce a custom class where all copy operations are removed to better demonstrate possible outcomes under different circumstances:

    struct MyClass {
    
        MyClass() {
            std::cout << "Default constructor" << std::endl;
        }
    
        MyClass(const MyClass&) = delete;
    
        MyClass& operator=(const MyClass&) = delete;
    
        MyClass(MyClass&&) {
            std::cout << "Move Constructor" << std::endl;
        }
    
        MyClass& operator=(MyClass&&) {
            std::cout << "Move Assignment" << std::endl;
            return *this;
        }
    
    };
    

    And change the parameter signature of your method accordingly:

    - (void)setInst:(MyClass)inst {
        _inst = std::move(inst);
    }
    

    Initialise the parameter with a temporary

    Assuming the method in the sample belongs to a class named TDWObject the following code will compile just fine:

    [[TDWObject new] setInst:MyClass{}];
    

    Under C++17, you will find the default constructor and the move assignment called:

    Default constructor
    Move Assignment
    

    The default constructor is called for the temporary, and thanks to guaranteed copy elision, neither copy nor move constructor is needed to initialise the parameter inst of the method with the temporary. The move assignment is straightforward - it happens when assigning result of std::move(inst) operation. If you use C++11 or C++14, standard doesn't guarantee copy elision, but clang will do it anyway. Some compilers use move-semantic instead, but overall for a temporary this code should work just fine.

    Initialise the parameter with a moved named variable

    Another option is to cast any named variable to an rvalue, and it will still allow to initialise the parameter without any issues:

    MyClass inst;
    [[TDWObject new] setInst:std::move(inst)];
    

    The difference in this case, is that the function parameter will actually call the move constructor without elision optimisation:

    Default constructor
    Move Constructor
    Move Assignment
    

    Initialise the parameter with a named variable

    And here is the broken scenario:

    TDW::MyClass inst;
    [self setInst:inst];
    

    This will not work of course, because the parameter needs to call the copy constructor, which is marked deleted. The good thing about it, this code will never compile, and you will spot the problem straight away.


    Considering alternatives

    First of all I don't really think that Objective-C properties are compatible with non-copyable C++ classes and decided to give my own answer to the question linked, which you can review here.