在类中存储对象的const引用

15

这听起来像是一个基础问题,但我没有找到任何全面的答案,所以在这里给出。考虑以下代码片段:

struct A {
    const std::string& s;
    A(const std::string& s) : s(s) {}
};

int main() {
    A a("abc");
    std::cout << a.s << std::endl;
    return 0;
}

演示

据我所知,这是未定义行为(UB)。字符串字面量“abc”在构造函数中绑定到了const std::string&,创建了一个临时字符串对象。它还绑定到了引用a.s,并在a被构造后立即被销毁。也就是说,常量引用不能链式延长生命周期。悬空引用,崩溃。在这种特殊情况下,在 ideone.com 上我看不到任何输出,但是任何事情都可能发生(记得迅猛龙吗)。

好的,这一点很清楚。但是如果这实际上正是我们的目的:我们想要存储一个常量引用到一个对象?到一个现有的对象,而不是临时对象?这听起来像是一个非常自然的任务,但我只想到了一种(几乎)自然的解决方案。通过std::reference_wrapper接受构造函数的参数,而不是引用:

    A(std::reference_wrapper<const std::string> r) : s(r) {}

由于 std::reference_wrapper 从临时变量中删除了构造函数:

reference_wrapper( T&& x ) = delete;

这个与预期一样有效。但是,这不是很优雅。我能想到的另一种方法是接受转发引用 T&& 并使用 std::enable_if 拒绝除 const l-value 字符串之外的所有内容。我认为这甚至更不优雅。

还有其他方法吗?

更新 另一个问题:这是否是 std::reference_wrapper 的合法用法,还是可能被认为过于特定?


1
A 为什么不能只有一个普通的 std::string?即使 A::s 接收到左值并且引用打破了赋值运算符,它也很可能会悬空。这是否有用例或纯粹是学术性的? - nwp
3
A(const std::string &&) = delete; 可能会实现你想要的效果,但我认为这并不是一个好主意。 - nwp
@nwp 这在实用类中可能非常有用,我们可以控制其实例的生命周期,并保证它们不会超出传递对象的生命周期。我使用 std::string 表示昂贵的可复制对象。另一种更安全的解决方案是在此对象上存储 shared_ptr。但是,在简单情况下,这可能过于繁琐。 - Mikhail
@nwp 我印象深刻的一个用例是无复制字符串视图。 - Joel Cornett
我认为你的解决方案实际上非常不错。如果你想让它读起来更好一些,你可以使用一个别名模板来代替std::reference_wrapper。 - Vaughn Cato
2个回答

7
我认为自然的解决方案就是像reference_wrapper一样,防止从临时对象进行构造。
struct A {
    const std::string& s;
    A(const std::string& s) : s(s) {}
    A(std::string&&) = delete;
};

还要记住的是,如果一个类拥有一个引用类型的数据成员,则默认情况下该类是不可赋值的(甚至不能进行移动赋值),而且实现赋值运算符通常比较困难。因此,您应该考虑存储指针,而不是引用:

struct A {
    const std::string* s;
    A(const std::string& s) : s(&s) {}
    A(std::string&&) = delete;
};

2
好的回答。唯一的缺点是,如果我们有多个这样的参数,删除所有不需要的选项将会非常困难。 - Mikhail
1
我不喜欢指针方法 - 它没有语法信息,因此无法为 nullptr。 - Mikhail
@Mikhail 我认为一个不完美但可用的对象比一个纯粹但无法使用的对象更优;-) 不过,你可以使用类似于 Core Guideline library 中的 not_null<T> - Angew is no longer proud of SO
是的,在这里也考虑了“not_null”。 - Mikhail

1

Andrzej Krzemieński提供了一个非常简单的lvalue_ref类,它位于轻便且方便的"explicit"库中,可以清晰地解决这个问题:

struct Processor
{
  Big const& _big;
  explicit Processor(lvalue_ref<const Big> b) : _big(b) {}
};

const Big b {};
Processor p {b}; // ok
Processor q {Big{}}; // error (temporary)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接