8

替代问题的标题是: 如何明确地让编译器为特定翻译单元中的编译器生成的构造函数生成代码?

我们面临的问题是,对于一个代码路径,如果一个对象的 copy-ctor 调用没有被内联,即如果这个构造函数是手动实现的,那么结果——经过彻底测量——性能会更好(大约 5%) 。(我们注意到这一点是因为在代码清理期间,删除了该类(17 个成员)的多余显式实现的复制 ctor。)

编辑:请注意,我们已经检查了生成的汇编代码,并确保内联和代码生成正在发生,正如我为两个不同的代码版本所描述的那样。

我们现在面临的选择是直接将手动 copy-ctor 代码放回原处(它与编译器生成的代码完全相同)或找到任何其他内联此类的 copy-ctor 的方法。

是否有任何方法(对于 Microsoft Visual C++)在特定翻译单元中显式实例化编译器生成的类函数,或者它们是否总是内联在使用它们的每个翻译单元中?(也欢迎对 gcc 或其他编译器发表评论,以更好地了解情况。)


由于前 2 个答案显示了一些误解:编译器生成的类函数只有在用户既没有声明也没有定义的情况下才由编译器本身生成。因此,不能对它们应用任何修饰符,因为源代码中不存在这些函数。

struct A {
  std::string member;
};

A有一个默认和复制 ctor、一个 dtor 和一个复制运算符。这些函数都不能通过一些 declspec 修改,因为它们不存在于代码中。

struct B {
  std::string member;
  B(B const& rhs);
};

B现在有一个用户提供的副本 ctor 并且用户必须实现它。编译器不会为它生成代码。


怀疑者的更多背景:-) ...

此代码是使用 MS Visual C++ 编译的,但它是为嵌入式(类似)(实时)系统而链接的。性能是通过在这个系统上计时来衡量的,因此我认为计时的人会有一些不错的数字。

通过比较两个代码版本来执行测试,其中唯一的区别是这一类的内联与非内联复制ctor。内联代码的时间差了大约 5%。


进一步检查发现我有一个错误:编译器将为复杂的复制构造函数生成单独的函数。它将自行决定执行此操作,并且还取决于优化设置。所以在我们的例子中,编译器在我们的特定情况下做错了事情。从到目前为止的答案来看,我们似乎无法告诉编译器。:-(

4

6 回答 6

5

$12.1/5-“隐式声明的默认构造函数是其类的内联公共成员。”。

所以我们无能为力。隐式构造函数必须是内联的。在这方面的任何其他行为都可能是扩展

话说回来,

您的手动复制构造函数(您在代码清理期间删除)很可能正在做正确的事情。例如,如果您的类中的成员之一(共 17 个)是指针成员,则手动复制构造函数很可能会处理深层复制(因此会影响性能)。

因此,除非您仔细检查您的手动复制构造函数,否则不要考虑删除它并依赖(可能有问题的)隐式复制构造函数(在您的上下文中)

于 2010-10-06T08:16:40.930 回答
3

我高度怀疑内联与它有什么关系。如果编译器内联编译器生成的复制ctor,为什么它不内联显式定义的?(编译器的优化启发式失败如此严重以致内联代码慢 5% 也是不寻常的)

在下结论之前,

  • 检查生成的程序集以验证两个版本是否确实执行完全相同的操作(并且以相同的顺序,使用相同的程序集等等,否则这可能是您的性能差异的根源)
  • 检查编译器生成的是否实际上内联的,而手动定义的不是.

如果是这种情况,您能否使用此信息更新您的问题?

C++ 中没有办法指示编译器生成的函数是否应该内联。甚至诸如特定于供应商的扩展也__declspec(noinline)不会帮助您,因为您明确地将函数的所有责任移交给编译器。所以编译器选择如何处理它,如何实现它以及是否内联它。你不能既说“请帮我实现这个功能”,同时又说“请让我控制这个功能的实现方式”。如果你想控制这个功能,你必须实现它。;)

在 C++0x 中,这可能是可能的(取决于这些特定于供应商的扩展如何与声明为的函数交互= default)。

但同样,我不相信内联是问题所在。最有可能的是,这两个函数只会导致生成不同的汇编代码。

于 2010-10-06T09:27:00.003 回答
0

__declspec(noinline)

文档说它仅适用于成员函数,但实际上它也适用于自由函数。

于 2010-10-06T08:15:26.147 回答
0

通常最好将其隔离为您知道有问题的几个核心类型。示例一:

class t_std_string {
    std::string d_string;
public:
    /* ... */

    /* defined explicitly, and out of line -- you know what to do here */
    t_std_string();
    t_std_string(const std::string& other);
    t_std_string(const t_std_string& other);
    ~t_std_string();

    inline std::string& get() { return this->d_string; }
    inline const std::string& get() const { return this->d_string; }
    /* ... */
};

struct B {
    t_std_string member;
    /* 16 more */
    /* ... */
};

或者你可以免费拿一些。示例 b:

/* B.hpp */

struct B {
private:

    /* class types */
    struct t_data {
        std::string member;

        /* 16 more ... */
    public:
        /* declare + implement the ctor B needs */

        /* since it is otherwise inaccessible, it will only hurt build times to make default ctor/dtor implicit (or by implementing them in the header, of course), so define these explicitly in the cpp file */
        t_data();
        ~t_data();

        /* allow implicit copy ctor and assign -- this could hurt your build times, however. it depends on the complexity/visibility of the implementation of the data and the number of TUs in which this interface is visible. since only one object needs this... it's wasteful in large systems */
    };
private:

    /* class data */
    t_data d_data;
public:
    /* you'll often want the next 4 out of line
       -- it depends on how this is created/copied/destroyed in the wild
     */
    B();
    B(const B& other);
    ~B();
    B& operator=(const B&);
};

/* B.cpp */

/* assuming these have been implemented properly for t_data */
B::B() : d_data() {
}

B::B(const B& other) : d_data(other) {
}

B::~B() {
}

B& B::operator=(const B&) {
    /* assuming the default behaviour is correct...*/
    this->d_data = other.d_data;
    return *this;
}
/* continue to B::t_data definitions */
于 2010-10-06T09:21:55.127 回答
0

您可以使用某种嵌套对象。通过这种方式,嵌套对象的复制构造函数可以保留为免维护默认值,但是您仍然可以显式创建一个可以声明 noinline 的复制构造函数。

class some_object_wrapper {
    original_object obj;
    __declspec(noinline) some_object_wrapper(const some_object_wrapper& ref) 
        : obj(ref) {}
    // Other function accesses and such here
};

如果你很绝望,你可以在 .lib 中单独编译有问题的类并链接到它。将其更改为不同的翻译单元不会阻止 VC++ 内联它。另外,我不得不质疑他们是否真的在做同样的事情。如果手动复制构造函数与默认复制构造函数相同,为什么还要实现它?

于 2010-10-06T09:50:41.040 回答
0

添加我自己的结论并在不详细说明的情况下回答确切的问题:

  1. 不能 强制编译器,特别是 VC++,内联或不内联编译器生成的 ctor/dtor/等。——但是

  2. 优化器将根据自己的判断选择是内联编译器生成的函数(ctor)的代码还是为此代码生成“真实”函数。AFAIK 在这方面无法影响优化器的决定。

于 2010-11-05T07:53:23.330 回答