11

In the code below, I made p const because it will never point to any other int during Foo's lifetime. This doesn't compile, as the unique_ptr's copy constructor is called, which is obviously deleted. Are there any solutions besides making p non-const? Thanks.

#include <memory>

using namespace std;

class Foo 
{
public:
  //x is a large struct in reality
  Foo(const int* const x) : p(x) {};
  Foo(Foo&& foo) : p(std::move(foo.p)) {};
private:
  const unique_ptr<int> p;
};
Agrim Pathak
  • 3,047
  • 4
  • 27
  • 43

5 Answers5

15

The semantics of your move constructor are contradictory.

You have declared a const std::unique_ptr which will (uniquely) own the value it is initialised with. But you've declared a move constructor that should move that value into another object at construction.

So what do you think should happen to the std::unique_ptr in the 'temporary' being move constructed from?

If you want it to be release()ed you've violated its constness. If you want it to retain its value you've violated the constraint of std::unique which requires no more than one such object to own any given object. Checkmate.

This problem reveals a subtle limitation of the C++ language. It requires move semantics to leave the copied to and from as valid objects.

There are several quite reasonable proposals for 'destructive move' which would in truth better reflect what most uses of move are doing - take a value to here from there 'invalidating' what was there.

Google them. I haven't made a literature survey so don't want to recommend one.

Your alternatives here are to remove const or cast it way. I strongly recommend removing it. You can make sure the semantics of your class ensure the appropriate const-ness with no impact and no 'ugly suspect' const_cast.

#include <iostream>
#include <memory>

class Foo 
{
public:
  Foo(const int x) : p(new int(x)) {};
  Foo(Foo&& foo) :
    p(std::move(foo.p)) {
        
    };
    
    int get(void)const{
        return *(this->p);
    }
    
private:
     std::unique_ptr<int> p;
};

Foo getMove(){
    return Foo(88);
}

int main(){

    Foo bar(getMove());    
    std::cout<<bar.get()<<std::endl;
    
    return EXIT_SUCCESS;
}
Persixty
  • 8,165
  • 2
  • 13
  • 35
  • How would I use `const_cast`? I tried `const_cast` but received a compilation error: invalid use of const_cast with type ‘std::unique_ptr’, which is not a pointer, reference, nor a pointer-to-data-member type – Agrim Pathak Mar 22 '15 at 13:16
  • 1
    I don't condone it but the code you're looking for is `p(std::move(const_cast& >(foo.p)))` in the initializers for the move constructor. – Persixty Mar 22 '15 at 13:28
  • Thanks, that's the one line of code I was looking for in this thread (I understood why it wasn't compiling from the start). – Agrim Pathak Mar 22 '15 at 13:38
  • 1
    @AgrimPathak: Glad to help. I think. I think it is technically OK to violate constraints of an object in move semantics so long as it leaves a destructible object. – Persixty Mar 22 '15 at 19:18
3

To understand why your code does not compile, reflect how you have declared Foo class and how move semantics is generally implemented.

Declaring a const unique_ptr<T> p, you mean that p itself will be never modified, but you could still modify the pointed-to object because of T is not const.

But move works on an opposite assumption. This feature uses the idea that is allowed stealing resources from objects and leave them in a empty state (if an empty state make sense). If can be useful, think move as a sort of 'destructive' copy for the moved object.

Writing std::move(foo.p), basically you steal the resource pointed by foo.p and leave it in a safe state, that means assign foo.p to NULL. But foo.p was declared as const, so the operation is not permitted.

Please consider that in your case you don't need to declare p as a const unique_ptr<int>. Simply declare it as unique_ptr<int> and make sure that member functions are declared as const and non-member functions take it as const unique_ptr<int> p& parameter. In this way you are sure that p will never change along the object lifetime (except in case of move operation).

prisco.napoli
  • 606
  • 6
  • 9
2

It is because unique_ptr has only the move-constructor, which means the initialization argument to p cannot be const, while p is const. I think what you wanted was to declare

unique_ptr p;

instead of

const unique_ptr p;

class Foo {
  public:
    // x is a large struct in reality
    Foo(const int* const x) : p(x) {};
    Foo(Foo&& foo) : p(std::move(foo.p)) {};
  private:
    const unique_ptr<int> p;
};
peak
  • 105,803
  • 17
  • 152
  • 177
user557583
  • 33
  • 5
1

Concept of using std::unique_ptr is representing a sole ownership of an object. What you're trying to achieve is having a Foo class own an object (which is expressed by std::unique_ptr) and making it movable (your move constructor) which makes a contradiction. I would stick with std::unique_ptr or make it shared using std::shared_ptr.

You might want to read this: Smart Pointers: Or who owns you baby?

Community
  • 1
  • 1
szulak
  • 703
  • 7
  • 16
  • 1
    How does it make a contradiction? It's a move, not a copy? – Emil Laine Mar 22 '15 at 13:05
  • The contradiction is a sole ownership of the object and moving it - after instantiating an object of class Foo it owns a "p" - it can not be moved later and cause another object own "p", since its owned by the previous object. – szulak Mar 22 '15 at 13:13
  • Ownership can move. Why not? If it is done explicitly. – Sebastian Jul 17 '22 at 20:27
0

If you want to prevent transfer of ownership, you can use a const std::unique_ptr<T>. This is not very useful.

If you want to prevent modifying the object it holds, you can use a std::unique_ptr<const T>.