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.