允许类的用户移动私有成员

Allowing users of a class to move private members

本文关键字:成员 移动 用户 许类      更新时间:2023-10-16

假设我有这个类:

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类的用户有机会移动有效负载,而不必复制它。这样做的最佳方法是什么?

我可以想出以下方法:

  1. 添加一个返回可变引用的Payload& payload()重载。用户可以这样做:

    Payload mine = std::move(message.payload())

  2. 不要假装我正在封装payload_,只需将其作为公共成员即可。

  3. 提供takePayload成员功能:

    Payload takePayload() {return std::move(payload_);}

  4. 提供此备用成员功能:

    void move(Payload& dest) {dest = std::move(payload_);}

  5. (由Tavian Barnes提供)提供一个使用ref限定符的payloadgetter重载:

    Payload0

    Payload payload() && {return std::move(payload_);}

替代方案#3似乎是在std::future::get,overload(1)中完成的。

任何关于最佳替代方案(或另一种解决方案)的建议都将不胜感激。


编辑:以下是我试图完成的一些背景。在我的实际工作中,这个Message类是一些通信中间件的一部分,它包含了用户可能感兴趣或不感兴趣的一堆其他元数据。我曾想过,用户可能想将有效载荷数据移动到他或她自己的数据结构中,并在收到原始Message对象后丢弃它。

似乎最一致的方法是使用选项5:

  1. 第一个和第二个选项似乎不建议使用,因为它们暴露了实现细节
  2. 当使用Message右值时,例如,函数的返回,它应该直接向前移动有效载荷,并且它使用第五个选项:

    Messsage some_function();
    Payload playload(some_function().payload()); // moves
    
  3. std::move(x)与表达式一起使用通常表明x的值不依赖于前进,其内容可能已经转移。第五个选项与该符号一致。

  4. 使用相同的名称并让编译器确定内容是否可以移动,这在通用上下文中更容易:

    template <typename X>
    void f(X&& message_source) {
    Payload payload(message_source.get_message());
    }
    

    根据get_message()产生的是左值还是右值,有效载荷被适当地复制或移动。第三种选择没有带来好处。

  5. 返回值可以在复制省略避免进一步潜在复制或移动的上下文中使用获得的:

    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行的代码库中,我只需要做一次。

话虽如此,我确实需要做一次,这样做有可衡量的显著性能优势。以下是我对您建议的每种方法的看法:

  1. 添加有效负载&payload()重载,返回一个可变引用。用户可以这样做:

    Payload mine = std::move(message.payload())

这里的缺点是用户可以执行message.payload() = something else;,这可能会干扰不变量。

  1. 不要假装我正在封装payload_,让它成为公共成员

封装不是要么全有要么全无的事情;IMO你应该尽可能多地封装,或者至少合理。

  1. 提供takePayload成员函数:

Payload takePayload() {return std::move(payload_);}

如果您无法访问ref限定符,这是我最喜欢的解决方案(真的VC++?)。我可能会把它命名为movePayload()。有些人甚至可能更喜欢它而不是选项5,因为它更明确,不那么令人困惑。

  1. 提供此备用成员功能:

void move(Payload& dest) {dest = std::move(payload_);}

当返回值可用时,为什么要使用out参数?

  1. (由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_); }

我真的不确定哪一个更好。由于复制省略,两者的性能应该相同。当忽略返回值时,它们确实有不同的行为。