【问题标题】:Compiler error or correct behavior for static const member variable, variadic templates, and &&?静态 const 成员变量、可变参数模板和 && 的编译器错误或正确行为?
【发布时间】:2014-12-02 20:29:15
【问题描述】:

在尝试编译下面包含的代码时,我注意到一个奇怪的行为。我有4个文件如下

createshared.h:

#ifndef CREATESHARED_H_
#define CREATESHARED_H_

#include <memory>
#include <utility>

#ifdef USE_REFREF
template<typename T, typename... Args>
std::shared_ptr<T> create_shared(Args&&... args)
{
    class HelperClass : public T
    {
    public:
        HelperClass (Args&& ... nargs) : T(std::forward<Args...>(nargs)...) {}
        virtual ~HelperClass() = default;
    };

    return std::make_shared<HelperClass>(std::forward<Args...>(args)...);
}
#else
template<typename T, typename... Args>
std::shared_ptr<T> create_shared(Args... args)
{
    class HelperClass : public T
    {
    public:
        HelperClass (Args ... nargs) : T(nargs...) {}
        virtual ~HelperClass() = default;
    };

    return std::make_shared<HelperClass>(args...);
}
#endif

#endif

静态初始化类.h

#ifndef STATICINITCLASS_H_
#define STATICINITCLASS_H_

class StaticInitClass
{
public:
#ifdef INITIALIZE_IN_HEADER
    static const int default_i = 1;
#else
    static const int default_i;
#endif
    virtual ~StaticInitClass() = default;
    StaticInitClass() = delete;
protected:
    StaticInitClass(int i);
};

#endif

静态初始化类.cpp:

#include "staticinitclass.h"

#include <iostream>

#ifndef INITIALIZE_IN_HEADER
const int StaticInitClass::default_i = 2;
#endif

StaticInitClass::StaticInitClass(int i)
{
    std::cout << "Created with " << i << std::endl;
}

main.cpp:

#include "staticinitclass.h"
#include "createshared.h"
#include <memory>

int main(int argc, const char* argv[])
{
    auto shared = create_shared<StaticInitClass>(StaticInitClass::default_i);
}

没有标志,程序编译并运行良好。

$ g++ -std=c++11 main.cpp staticinitclass.cpp 
$ ./a.out 
Created with 2

好的,因为default_i是一个整数类型,我们可以在header中初始化它。让我们这样做

$ g++ -std=c++11 main.cpp staticinitclass.cpp -DINITIALIZE_IN_HEADER
$ ./a.out 
Created with 1

很好,仍然可以编译并且工作正常。现在,让我们添加 && 和 std::forward

$ g++ -std=c++11 main.cpp staticinitclass.cpp -DINITIALIZE_IN_HEADER -DUSE_REFREF
/tmp/cc3G4tjc.o: In function `main':
main.cpp:(.text+0xaf): undefined reference to `StaticInitClass::default_i'
collect2: error: ld returned 1 exit status

链接器错误。好吧,现在让我们尝试在 .cpp 中初始化我们的 default_i 成员

$ g++ -std=c++11 main.cpp staticinitclass.cpp -DUSE_REFREF
$ ./a.out 
Created with 2

它又可以工作了。使用 clang 会产生相同的结果,这会让我相信这不仅仅是一个孤立的编译器错误,而且可能是语言本身的某些东西阻止了静态初始化。我似乎无法理解为什么添加 && 会导致中断。

目前我在 Ubuntu 14.04 上使用 g++ 4.8.2 和 clang++ 3.5

知道使用 -DINITIALIZE_IN_HEADER 和 -DUSE_REFREF 时这里有什么问题吗?

【问题讨论】:

  • 为静态数据成员提供初始化器并不意味着您也提供了定义。当您组合两个 -D 选项时,您最终会使用该静态成员,因此需要为其提供定义。

标签: c++ c++11 rvalue-reference compiler-bug


【解决方案1】:

按照 §9.4.2 [class.static.data]:

3 如果一个非易失性 const 静态数据成员是整数或枚举类型,它在类定义中的声明可以指定一个大括号或相等初始化器,其中每个初始化器子句是赋值表达式 是常量表达式 (5.19)。 [...] 如果该成员在程序中被 odr-used (3.2),则该成员仍应在命名空间范围内定义,并且命名空间范围定义不应包含初始化程序。

换句话说,直接在标头中给 const 静态数据成员一个值并不意味着您不需要定义该数据成员。你应该在 staticinitclass.cpp 文件中有这个:

#ifndef INITIALIZE_IN_HEADER
const int StaticInitClass::default_i = 2;
#else
const int StaticInitClass::default_i; // this is what you don't have
#endif

绑定到引用(在您的情况下,转发引用 &amp;&amp; 被推断为 const 左值引用)计为该数据成员的 odr-use

如果您不使用转发引用并且您采用参数按值,则它不是该静态数据成员的 odr-use,因此不会引发链接器错误。

【讨论】:

  • 缺少的部分是仅使用该值并不能算作 odr-use。
  • @T.C.也添加了这个
  • 啊!我完全不知道使用 odr 的规范。谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-27
  • 2018-08-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多