【问题标题】:Implicit call of Class constructor from Initialisation of another class从另一个类的初始化隐式调用类构造函数
【发布时间】:2026-02-04 00:05:01
【问题描述】:

以下代码-sn-p 似乎有效:

#include <iostream>

class Filling{
public:
  const int num;
  Filling(int num_)
  : num(num_)
  { }
};
class Nougat{
public:
  Nougat(const Filling& filling)
  : reference(filling)
  { }
  const Filling& get_filling() const{ return reference; }
private:
  const Filling& reference;
};
class Wrapper{
public:
  Wrapper(const Nougat& nougat)
  : reference(nougat)
  { }
  const Nougat& get_nougat() const{ return reference; };
private:
  const Nougat& reference;
};

int main(){
  Filling filling(3);
  Wrapper wrap(filling); /*(0)*/
  std::cout << wrap.get_nougat().get_filling().num << std::endl;
}

虽然初始化不正确(在/*(0)*/),因为Wrapper 类应该接受Nougat 类,而是给它一个Filling 类。

由于构造函数需要引用,我相信编译器正在从构造函数参数创建一个临时的Nougat 类,然后将其传递给初始化Wrapper 类。

从临时获取引用会导致未定义的行为。

这是真的发生了吗?

如果是这样,这段代码如何编译?

【问题讨论】:

  • Nougat 来自Filling&amp; 的构造函数不是explicit,因此可用于隐式转换。 Wrapper wrap(filling); 被解释为Wrapper wrap(Nougat(filling));,创建一个临时的Nougat 对象并将其传递给Wrapper。然后临时对象被销毁,给wrap 留下一个悬空引用。随后使用该引用的尝试表现出未定义的行为。 “似乎有效”是未定义行为的一种可能表现形式。
  • 我添加了显式关键字:explicit Wrapper(const Nougat&amp; nougat) ...,它仍然可以编译...
  • 可以用“如何禁止这种隐式行为”来扩展这个问题吗?
  • 用于隐式转换的是 Nougat 构造函数,而不是 Wrapper 构造函数。

标签: c++ reference initialization


【解决方案1】:

Wrapper 的构造函数需要一个对 Nougat 的引用,但你给它一个 Filling。与 C++ 中的所有函数调用一样,在这种情况下考虑所有参数的隐式转换序列

隐式转换序列可以是例如从派生类引用到基类引用的强制转换,但由于 NougatFilling 没有这种关系,因此此处不适用。

隐式转换序列中另一个允许的步骤是用户定义的转换。这还包括目标类型的所谓转换构造函数,即未标记explicit 的构造函数。构造函数Nougat(const Filling&amp; filling) 就是这种情况,它允许从Filling 转换为Nougat

因此,可以调用此构造函数来构造Nougat 类型的临时对象作为隐式转换。生成的临时值也可以绑定到 const 左值引用。

为避免这种无意的转换,您应该将构造函数标记为explicit,至少如果它们只接受一个参数,除非您真的打算发生这种隐式转换。


wrapper的构造结束时,临时对象被销毁,因此在中获得的引用

wrap.get_nougat()

是悬空的,它的后续使用会导致未定义的行为。

【讨论】: