Reputation: 791
How can I define a @property of std::unique_ptr in interface part of objective c class?
@property std::unique_ptr<MyClass> ptr;
But I can define a shared pointer!
If I define unique pointer then I got error that:
cannot be assigned because of its copy assignment operator is implicitly deleted
Upvotes: 5
Views: 2615
Reputation: 8998
I don't really think, that unique_ptr
is a consistent choice for owning a resource in Objective-C. In C++ it's conventional because in C++ members commonly don't outlive their owners (which is a common practice to ease a challenging memory management in the language). Access to such resources are not given through the pointer itself, but with a raw pointer or reference (and this is how you actually say the client code, that it doesn't own the resource):
class MyClass {
...
std::unique_ptr<Resource> _res_ptr;
public:
void setRes(Resource res) {
_res_ptr = std::make_unique<Resource>(std::move(res));
}
Resource& getRes() {
return *_res_ptr;
}
...
};
Owning a resource beyond life of the resource owner is very common in Objective-C however:
id resource;
{
TDWObject *owner = [TDWObject new];
resource = owner.resource;
} // the owner is destroyed here, while the resource keeps on living
Moreover, semantic of non-copyable C++ objects cannot be expressed with Objective-C properties in a robust way. Consider the following set of attributes for it:
@property (assign, nonatomic) std::unique_ptr<Resource> resPtr;
It more or less works for setters, but for getters you cannot make a copy of the instance, because copy operations are deleted, and you must not std::move
the instance from getter, because it would mean that the client code takes away the ownership of the instance from your object. (P.S. making an artificial copy in the getter would violate the class contract). Moreover it imposes limitation of rvalue-references on your property, that is - the client code will have to use either temporaries or cast all lvalues to rvalues to set the data.
unique_ptr
What you probably want in reality is to give the client code freedom of passing any type of arguments to the setter (both lvalues and rvalues), and provide access to the property value without altering ownership.
Such a configuration can not be expressed with Objective-C property attributes and I suggest just introducing a pair of custom methods for accessing and setting this data, while owning it with a pointer:
@implementation TDWObject {
std::unique_ptr<Resource> _resPtr;
}
...
- (void)setInstance:(Resource)res {
_resPtr = std::make_unique<Resource>(std::move(res));
}
- (Resource&)instance {
return *_resPtr;
}
...
@end
unique_ptr
is so popular in C++ also applies here - you can easily predict the lifetime of the resource and be sure it never outlives the owner itself.nullptr
automatically.This approach is very straightforward and can be implemented with a synthesised Objective-C property:
@property (assign, nonatomic) Resource res;
shared_ptr
I believe this approach is much more consistent in the eyes of Objective-C developers because shared_ptr
use very familiar concept of reference counting:
@property (assign, nonatomic) std::shared_ptr<Resource> resPtr;
shared_ptr
exists and doesn't need to track lifetime of the owner object.unique_ptr
but still very effective, as it doesn't spawn any redundant copies of the resourceassign
attribute of the property).Upvotes: 1
Reputation: 304
As Ref[1], compiler will generate setter, getter and instance variable for @property.
This following is an example that is compiled without error:
// .h file
@interface IOCMixCpp : NSObject
{
std::unique_ptr<int> mTotal;
}
@property (nonatomic, readonly, assign) std::unique_ptr<int> total;
@end
// .mm file
@implementation IOCMixCpp
- (instancetype)init {
self = [super init];
if (self) {
mTotal = std::make_unique<int>(9);
}
return self;
}
- (void)setTotal:(std::unique_ptr<int>)total {
mTotal = std::move(total);
}
- (std::unique_ptr<int>)total {
// This line is error free.
return std::move(mTotal);
// There is an error in the following line:
// Error: Call to implicitly-deleted copy constructor of 'std::unique_ptr<int>'
// return mTotal;
}
@end
NOTE:
unique_ptr SHOULD be used in internal of the Objective-C class and SHOULD NOT declare property with unique_ptr type.
"Luckily the compiler will prevent you from doing something dumb like declaring a @property with a std::unique_ptr. If it didn’t, the first time you accessed the value using self.foo your class would lose ownership of the pointer." Ref[2]
Automatic Property Synthesis with Xcode 4.4
https://useyourloaf.com/blog/property-synthesis-with-xcode-4-dot-4/
Objective C, Encoding and You
https://medium.com/@dmaclach/objective-c-encoding-and-you-866624cc02de
Upvotes: 1
Reputation: 1772
Property synthesis is the culprit here. When you declare @property unique_ptr<MyClass>
, the compiler implicitly creates setter and getter functions, along with a backing variable.
The set function probably looks something like this:
-(void) setPtr:(std::unique_ptr<MyClass>)ptr {
_ptr = ptr;
}
That line in the set function invokes the copy assignment operator of std::unique_ptr which is intentionally deleted because std::unique_ptr uses move semantics. Remember, you can't copy unique_ptr's, you can only transfer ownership from one instance to another.
To get around this problem you either need define your own set and get functions that respect move semantics or you need to work with an ivar directly.
Here's an example set function that would work correctly.
-(void) setPtr:(std::unique_ptr<MyClass>)ptr {
_ptr = std::move(ptr);
}
Upvotes: 3