0

Below is the simplified version of a class that I am trying to design

class Class {
 public:
     Class(std::unique_ptr<int>&& ptr): ptr_(std::move(ptr)) {}
 private:
     // works only if I change to std::shared_ptr or remove const qualifier
     const std::unique_ptr<int> ptr_;
};

int main() {
  std::unique_ptr<int> ptr = std::make_unique<int>(1);
  Class c(std::move(ptr)); // Compiles
  // I need to construct a new instance d, but c is not needed anymore,
  // anything c owns can be nullified.
  Class d(std::move(c)); // Does not compile
  return 0;
}

I can not construct an instance d from the instance c due to the following error message:

Copy constructor of 'Class' is implicitly deleted because field 'ptr_' has a deleted copy constructor

However, if I change the ptr_ member to be of type const std::shared_ptr<int>, then everything works.

Is there a way to have a single constructor for the Class, which would support:

  • Constructing the class instance directly providing the arguments it needs
  • Constructing the class instance from another class instance, possibly destroying another instance in the process (i.e. c(std::move(d));
  • Allowing the class to have a member of type const unique_ptr

?

EDIT: constructing an instance d using std::move(c) also doesn't work:

class.cc:17:23: error: use of deleted function ‘Class::Class(Class&&)’ 17 | Class d(std::move(c)); | ^ class.cc:5:7: note: ‘Class::Class(Class&&)’ is implicitly deleted because the default definition would be ill-formed:

So far two solutions work for me, neither are perfect from readability point of view:

  1. Remove const qualifier:

    std::unique_ptr<int> ptr_;

    The problem is that this reads as: "ptr_ member might be modified when calling public methods"

  2. Change to shared pointer:

    const std::shared_ptr<int> ptr_;

    The problem is that this reads as: "we have multiple pointers pointing to the same data"

Any way to improve the design which wouldn't have the problems outlined in (1) and (2)?

23
  • 2
    Is there a way to have a single constructor for the Class, which would support: No. Commented Jan 15, 2023 at 13:08
  • 2
    @mercury0114 The canonical way of telling the intent that you don't need c any more afterwards is to pass std::move(c) instead of c, which will cause the move constructor to be used, which is already defined implicitly: Class d(std::move(c));. Commented Jan 15, 2023 at 13:17
  • 4
    Can you design a copy constructor when the class has a member of type const std::unique_ptr? Yes, you can do that. Is there a way to have a single constructor for the Class...? No, you'll have to write the copy constructor yourself, and consider writing a move constructor, and probably will want/need a copy assignment and move assignment. Commented Jan 15, 2023 at 13:29
  • 2
    "constructing an instance d using std::move(c) also doesn't work:": Yes, sorry about that. I didn't notice you made the pointer const as well. Making the pointer const means that you intent it to be impossible for it to relinquish ownership. If you want to be able to transfer ownership, then it must not be const. Commented Jan 15, 2023 at 14:03
  • 3
    @mercury0114: "const members are great, if you can make something const, you should" And making members const has consequences. The object cannot be assigned to at all. This makes interacting with such types way harder than it needs to be. Commented Jan 15, 2023 at 16:35

1 Answer 1

4
  • Constructing the class instance from another class instance, possibly destroying another instance in the process (i.e. c(std::move(d));
  • Allowing the class to have a member of type const unique_ptr

This intersection of desires is inherently contradictory.

Moving is a modifying operation. If the object is const, you cannot move from it.

The entire purpose of unique_ptr is that there is one instance in the program which uniquely owns (and will destroy) the object it points to. Therefore, it cannot be copied, because a copy is a non-modifying operation. If it could be copied, then two objects would try to own the same object.

You cannot copy from a unique_ptr. You cannot move from a const object of any kind. Therefore, if your object has a const unique_ptr, it cannot get a pointer from some other instance of that object.

By declaring a member to be a const unique_ptr<T>, you are also declaring that the object can be neither copied nor moved. Those are the consequences of your code choices.

So you're going to have to decide what is more important: the member being const or the ability to copy/move from this object.

Broadly speaking, const members create more problems than they usually solve. If the member is private, then that is usually good enough protection; users with direct access to the class should be able to know what each such function should and should not modify. And if they make a mistake, the code in which that mistake can appear is pretty limited.

Sign up to request clarification or add additional context in comments.

1 Comment

Broadly speaking, const members create more problems than they usually solve - I don't agree with this claim, but the rest of the accepted answer makes sense, thanks!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.