【问题标题】:Who calls the copy constructor when passing by value?传值时谁调用了拷贝构造函数?
【发布时间】:2025-12-19 07:35:06
【问题描述】:

好吧,我有一个小问题要解决我的问题(也许还有其他问题 ^^)。所以让我们在 C++ 中假设以下内容:

class A {
  public:
    A() { }
    A(const A& src) { }
};

class B {
  public:
    B() { }
    void foo(A valuePassing) { }
};

int main(int argc, char* argv[])
{
    A passed;
    B obj;

    obj.foo(passed);        

    return EXIT_SUCCESS;
}

在这段代码中,谁负责调用Acopy constructor?问题可以概括为:当按值传递时,谁负责变量的副本,调用者还是被调用者?在这种特定情况下,B 会在接收到 var 时调用复制构造函数,还是 main() 会调用它并将副本发送到 B 的方法?

我的第一个想法是,为了维护由 C++ 可访问性引起的保护,应该由调用者来完成复制工作。但我需要确定这一点。每个编译器都通用吗(不明白为什么不,但编译器之间的差异有时很奇怪)?即VC++和gcc。

提前感谢您的回答;)

编辑:
如果有人想知道,这是关于实现由Georg Fritzsche 创建的Pass Key pattern。事实上,正如一些人所说,使其不可复制是一个好主意,以防止不太聪明的开发人员创建一个函数,将密钥提供给来自被授予密钥的类的任何人。但是使其不可复制也可以防止标准值参数传递(至少应该如此,我发现 Visual C++ 编译器不遵循标准并且实际上可以接受...)

我解决问题的想法是创建一个基类PassKey,它具有受保护的复制构造函数和赋值运算符。然后每个PassKey 类都将从这个类继承。然后需要使用密钥的函数通过 value 请求密钥作为第一个参数。这样,即使 smartass 开发人员执行前面提到的功能并提供对密钥的引用,尝试使用它的外部类也将无法使用它,因为它需要访问密钥的 copy constructor,它只能访问受保护和授予的类。

我说得够清楚还是需要澄清一下? :S

【问题讨论】:

  • 这种差异会有任何明显的影响吗?当然,最重要的是,在某个时候,会制作一份副本。
  • 你说的“谁”是什么意思?编译器在main 的上下文中插入对复制构造函数的调用。必须在调用函数之前初始化参数。
  • 我想调用代码导致通过调用函数并传递passed来发生复制,但询问什么是“负责”并没有真正意义.如果有的话,参数的初始化负责它。
  • 内联使问题变得复杂,在这个特定的代码中,甚至可能没有B::foo的“调用”。复制可能仍然会发生,但是如果函数是内联的,你怎么说“谁”做的呢?
  • 既不是调用者也不是被调用者。编译器。都夸编译器

标签: c++


【解决方案1】:

obj.foo(passed); 将产生以下由编译器生成的步骤序列:

  1. obj的指针放到栈帧上(this参数)
  2. 创建一个temp 变量(这是调用复制构造函数的地方)
  3. temp 放到栈帧上
  4. 致电foo()
  5. 一旦从函数返回 - 清除堆栈(这是调用析构函数的地方)

【讨论】:

  • 这是由 C++ 标准保证的吗?
  • 不是标准的,这是“通常”的实现
  • 根据 §8.5.3 中的规则保证临时创建和使用复制初始化。
  • 哇,谢谢@MichaelFoukarakis。您的意思是标准保证函数/方法的调用者将调用按值发送给函数的变量的复制构造函数? (此时main会调用A的拷贝构造函数来拷贝passed并将拷贝交给B.foo?)
  • 在这种情况下,它是。有关更多信息,请参阅this question 和答案。
【解决方案2】:

现在我将尽可能笼统地回答。

简短的回答是调用构造函数是编译器的工作。据我所知,它也将由调用者构造。但是,具体如何完成可能取决于您使用的调用约定。

存在多种调用约定,一个编译器可能支持多个,例如this page at MSDN。不同的架构可能需要特定的约定。

对于 x86,一个调用约定就像 YePhIck 回答的那样,而在例如 ARM 平台上,地址将直接放在一个寄存器中,然后再跳转到函数。并且不会被压入堆栈(除非您有超过 4 个参数)。

复制对象是否永远是被调用者的责任?我不知道这样的调用约定,但它可能存在。

【讨论】:

  • 是的,刚刚检查了我使用 cdecl 作为我的调用约定(我记得配置它是因为我不想使用默认的 VC++ 调用约定,因为某个特定的功能,不记得是哪个)。重要的是调用者调用了复制构造函数,所以谢谢;)
  • 出于好奇,这有什么关系?
  • 啊,刚刚阅读您的编辑,所以问题是:当函数 X 被调用时,这个受保护的构造函数是否在范围内...
  • 是的,目的是希望构造函数不被调用函数调用,因为这会完全破坏 PassKey 模式的安全性;)但是如果@MichaelFoukarakis 确认它是标准的一部分,那么PassKey 不仅比 Attorney-Client 惯用语更易于阅读,而且更安全!
  • 想要投票,但我的代表不允许 =/ 为什么参与不计入代表 :( 无论如何,我在 cmets 中投票给你 :D
【解决方案3】:

那么,B 类如何自己调用函数呢?您只有两个地方可以调用复制构造函数:main() 和 foo()。而且我看不到任何来自 foo 的电话,所以答案是显而易见的。 并且不要忘记 foo() 的返回类型。

【讨论】:

  • 对 foo 的类型很抱歉,这是一个示例:S 好吧,因为调用不明确,我只是想确定一下。调用是在 foo 方法执行期间隐式完成的;)