说我有这个班:
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_;
};
其中Payload
可能很大并且复制起来很昂贵。
我想让这个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-qualifier的payload
getter重载:
const Payload& payload() const {return payload_;}
Payload payload() && {return std::move(payload_);}
替代#3似乎是在std::future::get,重载(1)中完成的。
对于什么是最佳替代方案(或另一种解决方案)的任何建议将不胜感激。
编辑:以下是我正在努力完成的一些背景知识。在我的实际工作中,这个Message类是一些通信中间件的一部分,并包含一堆用户可能或可能不感兴趣的其他元数据。我原以为用户可能想要将有效负载数据移动到他的或者她自己的数据结构,并在收到后丢弃原始的Message对象。
答案 0 :(得分:2)
似乎最一致的方法是使用选项5:
当使用Message
rvalue时,例如,从函数返回时,应该直接移动有效负载并使用第5个选项:
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()
是否产生左值或右值,有效地复制或移动有效负载。第三种选择并没有产生这种好处。
返回该值可以在copy-elision避免进一步潜在副本或移动的上下文中使用获得的值:
return std::move(message).payload(); // copy-elision enabled
这是第四种选择不会产生的。
在资产负债表的负面,很容易错误地尝试移动有效载荷:
return std::move(message.payload()); // whoops - this copies!
请注意,第5个选项的其他重载需要以不同方式声明:
Payload payload() && { return std::move(this->payload_); }
Payload const& payload() const& { return this->payload_; }
// this is needed --^
答案 1 :(得分:2)
我建议的第一件事就是根本不做这件事,如果你能提供帮助。允许您的私有数据成员从中断封装移动(甚至比返回const引用更糟)。在我的~35,000行代码库中,我需要完成一次。
话虽如此,我确实需要做一次,并且这样做有可衡量的显着性能优势。以下是我对您建议的每种方法的看法:
- 醇>
添加有效负载&amp; payload()重载,返回一个可变引用。然后用户可以这样做:
Payload mine = std::move(message.payload())
这里的缺点是用户可以执行message.payload() = something else;
,这可能会弄乱您的不变量。
- 停止假装我正在封装有效负载_并将其设为公共成员。
醇>
封装不是一种全有或全无的东西; IMO你应该尽可能多地进行封装,或者至少是合理的。
- 提供takePayload成员函数:
醇>
Payload takePayload() {return std::move(payload_);}
如果您无法访问ref-qualifiers(认真 VC ++?),这是我最喜欢的解决方案。我可能会将其命名为movePayload()
。有些人甚至可能更喜欢选项5,因为它更明确,更少混淆。
- 提供此备用成员函数:
醇>
void move(Payload& dest) {dest = std::move(payload_);}
为什么在返回值时会使用out参数?
- (Tavian Barnes提供)提供有效载荷吸气器过载 使用ref-qualifier:
醇>
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
移出两个值,情况会更糟;对于不熟悉模式的人来说,双std::move()
会非常混乱。
另一方面,这比其他方法更安全,因为你只能从被视为xvalues的Message
移动。
有一个小变化:
Payload&& payload() && { return std::move(payload_); }
我真的不确定哪一个更好。由于复制省略,两者的表现应该相同。当忽略返回值时,它们会有不同的行为。