1

I've found out that unique_ptr can point to an already existing object. For example, I can do this :

class Foo {
public:
  Foo(int nb) : nb_(nb) {}
private:
  int nb_;
};

int main() {
  Foo f1(2);
  Foo* ptr1(&f1);
  unique_ptr<Foo> s_ptr1(&f1);

  return 0;
}

My question is :

If I create a class with unique_ptr< Bar > as data members (where Bar is a class where the copy constructor was deleted) and a constructor that takes pointers as argument, can I prevent the user from passing an already existing object/variable as an argument (in that constructor) (i.e. force him to use the new keyword) ? Because if he does, I won't be able to guarantee a valide state of my class objects (the user could still modify data members with their address from outside of the class) .. and I can't copy the content of Bar to another memory area.

Example :

class Bar {
public:
  Bar(/* arguments */) { /* data members allocation */ }
  Bar(Bar const& b) = delete;

  /* Other member functions */

private:
  /* data members */
};

class Bar_Ptr {
public:
  Bar_Ptr(Bar* ptr) {
    if (ptr != nullptr) { ptr_ = unique_ptr<Bar> (ptr); }
  } /* The user can still pass the address of an already existing Bar ... */

  /* Other member functions */

private:
  unique_ptr<Bar> ptr_;
};
Desura
  • 165
  • 1
  • 4
  • 11
  • 1
    "*But I would not be able to do this with shared_ptr.*" ... why not? As far as I know, `shared_ptr`'s constructors don't have any guards against that either. – Nicol Bolas Apr 24 '16 at 14:36
  • "*and I can't copy the content of Bar to another memory area*" Can you really do that anyway? `unique_ptr` is non-copyable. – Nicol Bolas Apr 24 '16 at 14:41
  • Oh yeah, you're right about the first thing. I ran a little test file earlier and it didn't compile. Must have been something else. About the second thing, I thought about taking an object as argument and just declaring a unique_ptr pointing to a dynamically allocated memory (which would contain a copy of Bar) inside the constructor, but this is not possible if the copy constructor is deleted. – Desura Apr 24 '16 at 15:19

4 Answers4

3

No you can't. You can't stop people from doing stupid stuff. Declare a templated function that returns a new object based on the templated parameter.

3

You can't prevent programmers from doing stupid things. Both std::unique_ptr and std::shared_ptr contain the option to create an instance with an existing ptr. I've even seen cases where a custom deleter is passed in order to prevent deletion. (Shared ptr is more elegant for those cases)

So if you have a pointer, you have to know the ownership of it. This is why I prefer to use std::unique_ptr, std::shared_ptr and std::weak_ptr for the 'owning' pointers, while the raw pointers represent non-owning pointers. If you propagate this to the location where the object is created, most static analyzers can tell you that you have made a mistake.

Therefore, I would rewrite the class Bar_ptr to something like:

class Bar_ptr {
public:
    explicit Bar_ptr(std::unique_ptr<Bar> &&bar)
        : ptr(std::move(bar)) {}
    // ...
}

With this, the API of your class enforces the ownership transfer and it is up to the caller to provide a valid unique_ptr. In other words, you shouldn't worry about passing a pointer which isn't allocated.

No one prevents the caller from writing:

Bar bar{};
Bar_ptr barPtr{std::unique_ptr<Bar>{&bar}};

Though if you have a decent static analyzer or even just a code review I would expect this code from being rejected.

JVApen
  • 11,008
  • 5
  • 31
  • 67
  • I thought about moving a smart pointer, but then, what if the user decide to try using the old pointer anyway (for example, if he isn't aware that I'm moving his pointer) ? I can prevent him from doing so. Is it my fault or his fault ? – Desura Apr 24 '16 at 15:28
  • 1
    If you accept a unique_ptr, he has to move one in, in order to be able to call your function. So it's totally the callers fault if he doesn't call your API the right way. You can't make it more clear than by accepting a std::unique_ptr – JVApen Apr 24 '16 at 15:48
1

I've seen something similar before. The trick is that you create a function (let's call it make_unique) that takes the object (not pointer, the object, so maybe with an implicit constructor, it can "take" the class constructor arguments) and this function will create and return the unique_ptr. Something like this:

template <class T> std::unique_ptr<T> make_unique(T b);

By the way, you can recommend people to use this function, but no one will force them doing what you recommend...

NoImaginationGuy
  • 1,795
  • 14
  • 24
1

You cannot stop people from doing the wrong thing. But you can encourage them to do the right thing. Or at least, if they do the wrong thing, make it more obvious.

For example, with Bar, don't let the constructor take naked pointers. Make it take unique_ptrs, either by value or by &&. That way, you force the caller to create those unique_ptrs. You're just moving them into your member variables.

That way, if the caller does the wrong thing, the error is in the caller's code, not yours.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982