允许类的用户移动私有成员
Allowing users of a class to move private members
假设我有这个类:
class Message
{
public:
using Payload = std::map<std::string, boost::any>;
Message(int id, Payload payload)
: id_(id),
payload_(std::move(payload))
{}
int id() const {return id_;}
const Payload& payload() const {return payload_;}
private:
int id_;
Payload payload_;
};
其中CCD_ 1可能是大的并且复制起来昂贵。
我想让这个Message
类的用户有机会移动有效负载,而不必复制它。这样做的最佳方法是什么?
我可以想出以下方法:
添加一个返回可变引用的
Payload& payload()
重载。用户可以这样做:Payload mine = std::move(message.payload())
不要假装我正在封装
payload_
,只需将其作为公共成员即可。提供
takePayload
成员功能:Payload takePayload() {return std::move(payload_);}
提供此备用成员功能:
void move(Payload& dest) {dest = std::move(payload_);}
(由Tavian Barnes提供)提供一个使用ref限定符的
payload
getter重载:Payload
0Payload payload() && {return std::move(payload_);}
替代方案#3似乎是在std::future::get,overload(1)中完成的。
任何关于最佳替代方案(或另一种解决方案)的建议都将不胜感激。
编辑:以下是我试图完成的一些背景。在我的实际工作中,这个Message类是一些通信中间件的一部分,它包含了用户可能感兴趣或不感兴趣的一堆其他元数据。我曾想过,用户可能想将有效载荷数据移动到他或她自己的数据结构中,并在收到原始Message对象后丢弃它。
似乎最一致的方法是使用选项5:
- 第一个和第二个选项似乎不建议使用,因为它们暴露了实现细节
-
当使用
Message
右值时,例如,函数的返回,它应该直接向前移动有效载荷,并且它使用第五个选项:Messsage some_function(); Payload playload(some_function().payload()); // moves
-
将
std::move(x)
与表达式一起使用通常表明x
的值不依赖于前进,其内容可能已经转移。第五个选项与该符号一致。 -
使用相同的名称并让编译器确定内容是否可以移动,这在通用上下文中更容易:
template <typename X> void f(X&& message_source) { Payload payload(message_source.get_message()); }
根据
get_message()
产生的是左值还是右值,有效载荷被适当地复制或移动。第三种选择没有带来好处。 -
返回值可以在复制省略避免进一步潜在复制或移动的上下文中使用获得的:
return std::move(message).payload(); // copy-elision enabled
这是第四种选择不会让步的。
在资产负债表的反面,很容易错误地尝试移动有效载荷:
return std::move(message.payload()); // whoops - this copies!
请注意,第五个选项的另一个过载需要以不同的方式声明:
Payload payload() && { return std::move(this->payload_); }
Payload const& payload() const& { return this->payload_; }
// this is needed --^
我建议的第一件事是如果你能帮上忙,就不要这样做。允许您的私有数据成员从breaks封装中移动(甚至比返回对它们的const引用更糟糕)。在我大约35000行的代码库中,我只需要做一次。
话虽如此,我确实需要做一次,这样做有可衡量的显著性能优势。以下是我对您建议的每种方法的看法:
添加有效负载&payload()重载,返回一个可变引用。用户可以这样做:
Payload mine = std::move(message.payload())
这里的缺点是用户可以执行message.payload() = something else;
,这可能会干扰不变量。
- 不要假装我正在封装payload_,让它成为公共成员
封装不是要么全有要么全无的事情;IMO你应该尽可能多地封装,或者至少合理。
- 提供takePayload成员函数:
Payload takePayload() {return std::move(payload_);}
如果您无法访问ref限定符,这是我最喜欢的解决方案(真的VC++?)。我可能会把它命名为movePayload()
。有些人甚至可能更喜欢它而不是选项5,因为它更明确,不那么令人困惑。
- 提供此备用成员功能:
void move(Payload& dest) {dest = std::move(payload_);}
当返回值可用时,为什么要使用out参数?
- (由Tavian Barnes提供)提供一个有效负载吸气剂过载使用ref限定符:
const Payload& payload() const {return payload_;}
Payload payload() && {return std::move(payload_);}
不出所料,这是我最喜欢的建议:)。注意,您必须编写
const Payload& payload() const& { return payload_; }
Payload payload() && { return std::move(payload_); }
(const&
而不是const
),否则编译器将抱怨不明确的重载。
这个成语并非没有一些警告,尽管我相信斯科特·迈耶斯在他的一本书中提到了这个成语,所以它不会太糟糕。其中之一是调用者语法很奇怪:
Message message;
Payload payload = std::move(message).payload();
如果需要从Message
中移出两个值,情况会更糟;双CCD_ 26对于不熟悉该模式的人来说将是非常令人困惑的。
另一方面,这比其他方法安全得多,因为您只能从被视为x值的Message
移动。
有一个小变化:
Payload&& payload() && { return std::move(payload_); }
我真的不确定哪一个更好。由于复制省略,两者的性能应该相同。当忽略返回值时,它们确实有不同的行为。
- 基类中的默认析构函数禁用子类中的移动构造函数(如果有成员)
- 直接初始化不可复制、不可移动的成员,而不使用聚合初始化
- 在临时将成员带出时省略复制/移动
- 移动类的成员作为常量引用参数传递
- C++ - 移动具有固定大小的 c 样式数组成员的类的构造函数
- 如何为具有私有成员的派生类实现移动构造函数
- 我应该移动 std::exchange ed 成员吗?
- 移动具有常量成员的类的构造和分配
- 将参数传递给构造函数和成员函数时移动或复制
- 默认移动成员定义为已删除,而未定义特殊成员?
- C++模板成员初始化:用右值移动构造,但用左值移动引用
- 如何初始化非静态模板成员变量从临时工开始,即而无需复制或移动
- "<某些系统标头>:错误:"<在此处插入函数>"在移动源文件后不是"std"的成员
- 移动构造函数是自动生成的,即使成员没有移动构造函数?
- Clang-Tidy:移动构造函数通过调用复制构造函数来初始化类成员
- c++ 从成员函数创建新线程并移动对象和整个对象
- C++移动拥有指针成员的构造函数
- 编译器生成的移动成员在静态数据成员上功能
- QThread - 使用 moveToThread 将类成员移动到线程
- 将 std::forward_as_tuple() 结果传递给可能从该对象的 rvalue-reference 成员移动的多个函数?