Home > Net >  Removing copy ctor of 3rd party class
Removing copy ctor of 3rd party class

Time:09-07

I have a class that stores a large std::map. My understanding is that the idiomatic way to do this is:

class Foo {
 public:
  Foo(/* Note: passed by value */ std::map<Bar, Baz> large_map) : large_map_(std::move(large_map)) {}
 private:
  std::map<Bar, Baz> large_map_;
};

int main() {
  std::map<Bar, Baz> large_map;
  // Work hard to initialize large_map.
  Foo foo = Foo(std::move(large_map));
}

This transfers the ownership of large_map from main, to the constructor arg and then to Foo's member. The problem with this is that the code is hard to use properly and I discovered that someone somewhere created a Foo and forgot to move the map into the ctor:

void deep_dark_hidden_code() {
  std::map<Bar, Baz> large_map;
  // Work hard to initialize large_map.
  Foo foo = Foo(large_map); // Whoops! The author of this code forgot to std::move
}

I am looking for a way to write Foo which protects against such mistakes. My first thought was to use unique_ptr

class Foo {
 public:
  Foo(std::unique_ptr<std::map<Bar, Baz>> large_map_ptr) : large_map_(std::move(*large_map_ptr)) {}
 private:
  std::map<Bar, Baz> large_map_;
};

int main() {
  std::unique_ptr<std::map<Bar, Baz>> large_map_ptr = new std::map<Bar, Baz>;
  // Work hard to initialize large_map_ptr.
  Foo foo = Foo(std::move(large_map_ptr));
}

This code is essentially using unique_ptr as a hack to erase the copy constructor of std::map. My question is whether there is a more explicit way to do this. Some magic template make_uncopyable like:

class Foo {
 public:
  Foo(make_uncopyable<std::map<Bar, Baz>> large_map) : large_map_(std::move(large_map)) {}
 private:
  std::map<Bar, Baz> large_map_;
};

The desired effect is to leave the code in my main intact but prevent the code in deep_dark_hidden_code from compiling.

CodePudding user response:

The title appears to be a slight misnomer here (or is at least at odds with the contents with your question):

(At least in the example given), you do not want to remove the copy constructor i.e. Foo::Foo(const Foo& other), but rather prevent invokation of Foo's constructor with a non-movable argument.

As Mestkon pointed out (all credit to them - if they want to post it as an answer, just give me a yell and I'll remove mine), you could change Foo's constructor to require a std::map<Bar, Baz>&& large_map i.e.

Foo(std::map<Bar, Baz>&& large_map) : large_map_(std::move(large_map)) {}

A test at Godbolt confirms that the compiler will refuse to accept the Foo foo = Foo(large_map); in the deep_dark_hidden_code()., demanding the argument to be movable (as desired). This this might still run the risk of others "fixing" their code by simply slapping a std::move() around their large_map... and then attempting to continue using it after constructing Foo with it.

If you want really want to prevent the invokation of the copy constructor (here std::map), things become rather difficult, as you cannot change the the definition std::map to erase its copy constructor. I don't feel I have a good answer to this.

  •  Tags:  
  • c
  • Related