【问题标题】:Use a temporary as default argument in constructor在构造函数中使用临时参数作为默认参数
【发布时间】:2014-03-07 19:16:10
【问题描述】:

在 C++ 中,我们可以将对象分配给非常量引用。所以这行得通:

Foo &foo = Foo();

但是,C++ 不允许将临时值分配给非常量引用。所以这是不允许的:

Bar::Bar(Foo &foo = Foo()) {}

但这是允许的

Bar::Bar(const Foo &foo = Foo()) {}

我在这里有三个问题:

  1. 最后一种情况下 foo 的范围是什么?即使在构造函数退出后它仍然存在。根据我的阅读,如果将临时对象的生命周期分配给 const 引用,则它的生命周期会被修改,在这种情况下,它会占用引用的生命周期。默认参数(在本例中为 foo)的生命周期是多少?
  2. 我在 MSVC 中尝试了第二个示例,它没有抱怨。我还注意到临时的寿命仍然延长。所以我可以在构造函数退出后使用 foo 。这是关于什么的?
  3. 我的场景要求以这种方式为构造函数提供默认参数,我需要修改构造函数中的参数(所以我不能将其设为 const)。而且我还需要在构造函数退出后使用 foo 。满足这种情况的最佳设计是什么?

提前致谢。

【问题讨论】:

  • Foo &foo = Foo(); 这肯定行不通。
  • @dyp 除非您使用的是 MSVC。它有一个扩展,允许临时绑定到非常量引用。
  • @Angew 是的,使用/Za。不幸的是,如果没有这些扩展,许多 Microsoft 标头将无法编译。
  • 顺便说一下,“范围”是源代码中可以使用名称来引用声明的部分; “生命周期”是从构造函数完成到析构函数启动结束的运行时间。

标签: c++ c++11


【解决方案1】:

最后一种情况下foo的作用域是什么?

foo 是一个构造函数(同样适用于常规函数)参数,所以它的生命周期在the full expression containing the call to the constructor ends 时结束。 谢谢aschepler

默认参数的生命周期是多少,在本例中为 foo?

您通过将默认参数绑定到Foo const& foo 来延长它的生命周期,因此它的生命周期将匹配它所绑定的引用的生命周期,即直到构造函数主体退出。

我在 MSVC 中尝试了第二个示例,它没有抱怨。

如果您将警告级别设置为/W4,它会这样做;在这种情况下,它会警告您正在使用非标准扩展。 AFAIK,语义与前一种情况相同。

我的场景要求以这种方式为构造函数提供默认参数,我需要修改构造函数中的参数(因此我不能将其设为 const)。而且我还需要在构造函数退出后使用 foo 。

这取决于您是否要将其保存为Bar 的成员。如果是前者,请使用右值引用并移动参数

Bar::Bar(Foo&& foo = Foo()) : f_(std::move(foo)) {} // f_ is a member of type Foo

否则,只需省略默认参数。您还可以创建这两个重载以涵盖不同的情况。

【讨论】:

  • 构造函数的终结并不是临时生命的终结:stackoverflow.com/questions/12554619
  • @aschepler 感谢您指出这一点。但是在这种情况下,因为它是一个构造函数,这适用吗?我想不出如何将调用链接到构造函数或类似的东西,以便能够在外部表达式中使用该引用。
  • @aschepler 是的,再次感谢!我已经确定了答案。
【解决方案2】:
  1. foo 及其临时值在构造函数完成后消失。
  2. 这是一个不符合语言标准的 MS 扩展。
  3. 在这种情况下不要使用临时默认值。如果需要在构造函数完成后能够访问它,则需要在调用构造函数之前自己创建对象,并通过引用传入。

【讨论】:

【解决方案3】:

我的场景要求以这种方式为构造函数提供默认参数 我需要修改构造函数中的参数(所以我不能 使其成为常量)。而且我还需要在构造函数之后使用 foo 退出。满足这种情况的最佳设计是什么?

您可以在Bar 中创建一个optional<Foo>,如下所示:

struct Bar
{
    Bar() : optionalFoo(boost::in_place()), foo(*optionalFoo) {}
    Bar(Foo& f) : foo(f) {}
    Bar(Bar const&) = delete; // Compiler generated one is incorrect
private:
    boost::optional<Foo> optionalFoo;
    Foo&                 foo;
};

但是选择拥有自己数据的类会变得很棘手,尤其是在复制/移动方面。

【讨论】:

    【解决方案4】:
    1. 在这种情况下(与大多数情况一样),直接绑定到引用的临时绑定的生命周期会延长到引用的生命周期。因此,只要 foo 存在,即在构造函数的持续时间内,临时文件将是有效的。它将在包含构造函数调用的完整表达式结束时结束。

    2. MSVC 有一个扩展,允许临时对象绑定到非常量引用。它不是标准的,其他编译器不支持它。不要使用这个,除非你是(并且永远是)只为 MSVC 编码。

    3. 这取决于您实际想要达到的目标 - 您的问题中没有足够的信息来回答这个问题。如果您需要它在构造函数退出后仍然存在,您需要提供一个具有适当生命周期的实际对象。您可以提供每个线程的静态成员,例如:

      class Bar
      {
        static thread_local Foo ctor_default_foo;
      
      public:
        Bar(Foo &foo = ctor_default_foo) {}
      };
      

      请注意,这当然意味着由一个线程使用默认参数创建的所有对象将共享相同的Foo 对象。如果您需要为每个Bar 对象创建一个新对象,则必须更改您的设计。但要告诉你怎么做,我们需要更多地了解实际用例。

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-09
    • 1970-01-01
    • 2010-12-10
    • 1970-01-01
    • 2021-06-15
    相关资源
    最近更新 更多