为什么复制构造函数有时要显式声明为非内联的?

Why should copy constructors be sometimes declared explicitly non-inlined?

我无法理解关于内联和客户二进制兼容性的句子。有人可以解释一下吗?

C++ 常见问题 Cline,Lomow:

When the compiler synthesizes the copy constructor, it makes them inline. If your classes are exposed to your customers ( for example, if your customers #include your header files rather than merely using an executable, built from your classes ), your inline code is copied into your customers executables. If your customers want to maintain binary compatibilty between releases of your header files, you must not change an inline functions that are visible to the customers. Because of this, you will want an explicit, non inline version of the copy constructor, that will be used directly by the customer.

考虑将以下代码编译成静态库:

// lib.hpp
class
t_Something
{
     private: ::std::string foo;

     public: void
     Do_SomethingUseful(void);
};

// lib.cpp
void t_Something::
Do_SomethingUseful(void)
{
    ....
}

// user_project.cpp

int
main()
{
   t_Something something;
   something.Do_SomethingUseful();
   t_Something something_else = something;
}

现在,当 t_Something class 字段以某种方式发生变化时,例如添加一个新字段时,我们会遇到必须重新编译所有用户代码的情况。基本上由编译器 "leaked" 从我们的静态库到用户代码隐式生成的构造函数。

我想我明白这段话的意思了。不过,我绝不赞同这一点。

我相信,他们描述了您在开发库时的场景,并以 header 文件和 pre-compiled 二进制库部分的形式提供给您的客户。客户完成初始构建后,他们应该能够用更新的二进制部分替换二进制部分,而无需重新编译他们的应用程序——只需要重新链接。实现这一目标的唯一方法是保证 header 文件是不可变的,即在版本之间不改变。

我想,这个概念可能来自于 98 年的构建系统不够智能,无法检测 header 文件中的更改并触发受影响的源文件的重新编译。

现在所有这些都完全没有实际意义,事实上,这又是一个问题——因为多种原因,实际上有相当多的图书馆在努力成为 header-only 图书馆。

动态库(.dll.so)的二进制兼容性通常很重要。

例如您不想在 OS 上重新编译一半的软件,因为您更新了一些以不兼容的方式使用的低级库(并考虑安全更新的频率)。即使您想要,您通常甚至可能没有这样做所需的所有源代码。

为了使您的动态库更新兼容并实际产生效果,您基本上不能更改 public 头文件中的任何内容,因为那里的所有内容都直接编译到其他二进制文件中(即使在C 代码,这通常可以包括结构大小和成员布局,显然您也不能删除或更改任何函数声明)。

除了 C 问题之外,C++ 还引入了更多内容(虚函数的顺序、继承的工作原理等),因此可以想象,您可能会做一些更改自动生成的 C++ 构造函数、副本、析构函数等的操作. 同时保持兼容性。如果它们是 "inline" 与 class/struct 一起定义的,而不是在您的源代码中明确定义的,那么它们将直接被链接到您的动态库并使用那些自动生成的函数的其他 applications/libraries 包含,他们不会得到您更改后的版本(您可能甚至没有意识到已经更改!)。

它指的是库的二进制版本与该库中 header 更改之间可能发生的问题。有些变化是二进制兼容的,有些变化不是。对内联函数的更改,例如内联 copy-constructor,不是二进制兼容的,需要重新编译使用者代码。

您一直在单个项目中看到这一点。如果您更改 a.cpp,那么您不必重新编译所有包含 a.hpp 的文件。但是,如果您更改 header 中的接口,那么 header 的任何使用者通常都需要重新编译。这类似于使用共享库时的情况。

当想要在不更改其接口的情况下更改二进制库的实现时,保持二进制兼容性非常有用。这对于错误修复之类的事情很有用。

例如,假设某个程序使用 liba 作为共享库。如果 liba 在它公开的 class 的方法中包含错误,那么它可以更改内部实现并重新编译共享库,程序可以使用该 liba 的新二进制版本无需重新编译。但是,如果 liba 更改 public 契约,例如内联方法的实现,或将内联方法移动到外部声明,那么它会破坏应用程序二进制接口 (ABI) 和消费程序必须重新编译才能使用 liba.

的新二进制版本