【问题标题】:lvalue and rvalue getter, is it possible to remove them?lvalue 和 rvalue getter,是否可以删除它们?
【发布时间】:2020-12-15 18:31:00
【问题描述】:

假设我们有一个简单的类

struct MyClass {
    const std::string &getString() const & {
        return m_string;
    }

    std::string getString() && {
        return std::move(m_string);
    }

  private:
    std::string m_string;
};

正如我们所看到的,m_string 在我们无法修改它的意义上充当非可变变量。

这个结构还保留了这样一个事实:如果我们将MyClass 的一个实例移动到另一个实例,m_string 属性也会被移动。

现在,我们将尝试重构之前的结构:

struct MyClass {
    std::string m_string;
};

在这里,我们保留了可以访问或移动它的事实,但是我们失去了“不变性”......所以我试着这样写:

struct MyClass {
    const std::string m_string;
};

这里我们得到了不变性,但是,当我们移动对象时,我们失去了潜在的优化......

那么,是否有可能在不编写所有 getter 的情况下具有类似于第一个代码的行为?

编辑:std::string 只是一个例子,但这个想法必须适用于所有类型的对象

【问题讨论】:

  • 它是一个指针和一个大小,它会花费一点,但我可能会使用一个返回 std::string_view 的 getter,比如 struct MyClass { std::string_view getString(){ return m_string; } private: std::string m_string; };
  • @NathanOliver 感谢您的回答,但std::string 示例只是示例,我想要一个“通用”解决方案:)。我编辑了这个问题,因为它不清楚。
  • 也许您可以编写一个带有隐式转换运算符的包装器,一个用于右值引用,一个用于左值引用。然后你可以使用包装器作为成员,例如my_moveable<std::string> m_string;
  • @TedLyngmo 我正在尝试开发一个小库,因此,有时,我希望有这样的行为。但这更多是为了好奇:)
  • getString() && 重载仅在fnReturningMyClass().getString()std::move(myClassObj).getString() 等情况下被调用。你真的需要这种行为吗?它应该与将一个 MyClass 实例移动到另一个无关。相反,它用于窃取临时/生命终止MyClass 实例的内容以用于其他目的。

标签: c++ c++20


【解决方案1】:

那么,是否有可能在不编写所有 getter 的情况下具有类似于第一个代码的行为?

我想不出来。

话虽如此,为成员变量编写 getter 和 setter 的开销并不是很大的负担,以至于我会花太多时间考虑它。

但是,有些人认为成员变量的 getter 和 setter 没有为类添加足够的保护,甚至不用担心它们。如果您同意这种思路,您可以完全摆脱 getter 和 setter。

我已经多次对数据容器使用“无 getter 和 setter”原则,我发现它在许多用例中都很自然。

【讨论】:

  • 感谢您的回答 :)。我认为我可以接受我们应该避免 getter 和 setter 的事实。但是,对于 struct 之类的对象,我对 getter 和 setter 很满意 :)
【解决方案2】:

您可以使用模板包装器类型来实现此行为。似乎您想要一种与复制和移动构造和赋值一起工作的类型,但它只提供const 对包装对象的访问。您只需要一个带有转发构造函数、隐式转换运算符和取消引用运算符的包装器(在隐式转换不起作用时强制转换):

template<class T>
class immutable
{
public:
    template<class ... A>
    immutable(A&&... args) : member(std::forward<A>(args)...) {}

public:
    operator const T &() const { return member; }
    const T & operator*() const { return member; }
    const T * operator->() const { return &member; }

private:
    T member;
};

这适用于编译器生成的复制和移动构造和赋值。然而,该解决方案不是 100% 透明的。如果上下文允许,包装器将隐式转换为对包装类型的引用:

#include <string>

struct foo
{
    immutable<std::string> s;
};

void test(const std::string &) {}

int main()
{
    foo f;
    test(f.s); // Converts implicitly
}

但它需要额外的取消引用来强制在隐式转换不起作用的上下文中进行转换:

int main()
{
    foo f;
    //  std::cout << f.s;   // Doesn't work
    std::cout << *(f.s);    // Dereference instead
    //  f.s.size();         // Doesn't work
    f.s->size();            // Dereference instead
}

有人提议添加 . 运算符的重载,这将允许大多数情况按预期工作,而无需取消引用。但我不确定提案的当前状态。

【讨论】:

  • 不要忘记添加右值合格的运算符,否则你可能会遇到一些问题,但这个解决方案请我 :) 谢谢
  • @facetus 你错了,移动赋值运算符工作得很好。这是一个示例,我将std::string 替换为不可复制的类型:godbolt.org/z/ehxjq9。相反,std::shared_ptr 将需要用户定义的副本构造和分配来保持相同的行为。编辑:固定链接。
  • @facetus 我不确定你在说什么,我所拥有的 C++11 的最终草案没有这么说。如果用于带有 C++11 和 C++17 的 foo f; foo g; g = std::move(f);,则为 GCC 生成的程序集显示 foo::operator=(foo&amp;&amp;)。并且由于这应该是关于已经修复的 C++11 缺陷,因此它与这个问题(标记为 C++20)无关。我不得不假设我没有正确解释您的评论...
  • @facetus 感谢您的参考,但请注意,您引用的部分与此缺陷报告的目标不同。无论如何,我仍然看不出它与这个答案有什么关系,因为这不是 C++11 问题。
  • @facetus 请理解标准草案是大多数人可以访问的最好的东西。作为一般规则,对于典型的讨论,除非表明相关部分在最终版本中有所更改,否则应假定它们是准确的。
【解决方案3】:

解决方案是使用std::shared_ptr&lt;const std::string&gt;。指向不可变对象的共享指针具有值语义。使用shared_ptr::unique() 可以实现写时复制。请参阅 Sean Parent 演示文稿 47:46 https://youtu.be/QGcVXgEVMJg

【讨论】:

  • 一开始我不明白你的解决方案,视频对我来说很重要。这确实是一个干净而简单的解决方案,尽管控制块需要锁定以及它引入动态分配的警告值得一提。虽然通常这些并不重要。将来我可能会使用它而不是我自己的解决方案。
  • 我建议在答案本身中包含更多详细信息,即使可以通过外部链接获得相同的信息。网站的很大一部分用户不会打开外部链接,直接在答案中添加解释将有助于此答案覆盖更广泛的受众。
  • 我正在研究视频,很有趣。但是,用户可以这样做:myObject.shared = make_shared&lt;Object&gt;(),所以我必须在使用它之前研究它:)。非常感谢这个解决方案
  • 我在考虑在帖子中加入更多细节,但是 Sean 的演示非常基础,必须让每个人都观看,我想也许更少的细节会鼓励人们观看它。
【解决方案4】:

如果您愿意将复制和解散 ctor 声明为 =default 并使用 const_cast 作弊定义移动 ctor。

MyClass::Myclass()=default;
MyClass::Myclass(Myclass const&)=default;
MyClass::Myclass(Myclass && m)
:   m_string{std::move(const_cast<std::string&>(m.m_string))}{};

【讨论】:

  • 它将始终是未定义行为,因为m.m_string 始终是const 对象。
  • 当你做类似std::move(myClass).m_string的事情时它不会解决问题
  • @AntoineMorrier 这样的角落很危险;如果对象稍微复杂一点,则存在切片风险。至少我不想那样做。
  • @FrançoisAndrieux 我还没有看到将非静态数据成员保存在不同内存区域的平台。但是一旦对象是纯右值或 xvalue,我看不出删除 const 限定符有任何危险; dtor 即将被调用,它将丢弃 cv 限定符。 RValueness 是关于对象生命周期的。
  • @Red.Wave 没关系,语言禁止它。假设您可以预测编译器将如何修改它是错误的。至少,编译器有权检测 UB 并假设永远不会到达代码。见著名的time traveling UB。任何使用您的解决方案的代码本身都可能被该解决方案破坏。
猜你喜欢
  • 1970-01-01
  • 2013-12-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-06-22
  • 2020-09-25
  • 2015-02-27
相关资源
最近更新 更多