【问题标题】:Static constexpr data member initialized off-class, and a possible workaround?静态 constexpr 数据成员在类外初始化,以及可能的解决方法?
【发布时间】:2021-08-10 12:21:21
【问题描述】:

elsewhere 我知道,在 C++17 中,如果没有立即初始化,就无法声明静态 constexpr 数据成员(尽管yet elsewhere 他们使用了这样的示例)。

// --- in header ---
struct Data{
    LPCTSTR str;
    int i;
};

class C{
public:
    static constexpr Data MyData; // VS complains: this requires an in-class initializer
};

// --- in implementation file ---
constexpr Data C::MyData={
    _T("Hi"), 12345
};

我的代码暂时还必须在较旧的 Visual Studio 中编译,因为缺少 constexpr 概念很容易被#定义为

#if _MSC_VER<=1600 // pre-C++11 compiler?
    #define constexpr const
#endif

突然之间,代码库几乎可以兼容从 VS6 到 VS2019 的任何东西。如果通过 Visual Studio 中的某些隐藏命令行开关允许类外初始化,请纠正我。

所以我想出了一个解决方法,我不确定它的有效性。与数据成员不同,静态 constexpr 方法不必具有类内主体。所以我这样做:

// --- in header ---
struct Data{
    LPCTSTR str;
    int i;
};

class C{
public:
    static constexpr Data GetMyData();
};

// --- in implementation file ---
constexpr Data C::GetMyData(){
    constexpr Data D={
        _T("Hi"), 12345
    };
    return D;
}

然后我可以从 constexpr 中受益,但仍然可以在旧版 Visual Studio 下编译(借助上述 #define):

constexpr LPCTSTR Greeting=C::GetMyData().str;

并且还出人意料地在运行时获取返回对象的地址:

const Data *p=&C::GetMyData();
const LPCTSTR greeting=p->str;

这在我的应用程序中很棒而且很受欢迎!但是让我担心我收到的什么的地址?它是编译到生成的可执行文件中的对象的地址(并且在运行时驻留在受保护的内存中)还是在运行时真正创建的全局变量的地址?我当然希望在较新的 Visual Studios 下编译时出现更早的情况,而我不在乎 C++11 之前的版本下的情况。

提前致谢。

【问题讨论】:

  • const Data *p=&amp;C::GetMyData(); 是一个危险的指针。 GCC 甚至不会编译这段代码。 MS编译器accepts它作为语言扩展。
  • 拥有一个未知值的编译时间常数有什么意义?
  • 你应该有一些宏 CONSTEXPR 而不是 #define constexpr constconstexprconst 不可互换。例如:constexpr int&amp; C::get()const int&amp; C::get()
  • @Evg 我虽然是这样,但最好问! :-(

标签: c++ optimization compile-time-constant


【解决方案1】:

关键是constexpr并不是说“只在编译时存在”,而是“可以在编译时使用。所以只要你不访问constexpr 变量以要求在编译时可访问的值的方式,可以将其视为const

事实上,通过您发布的代码,您唯一能看到constexpr 好处的地方就是实现文件内部。在其他任何地方,它都别无选择,只能“降级”为常规的 const,因为在编译期间解析它的值是不可能的。

我认为您的大部分问题都可以通过将代码解构为等效示例来解决:

// MyClass.h
class MyClass {
  // static constexpr Data MyData;
  static const Data MyData;
};
// MyClass.cpp
namespace {
  constexpr Data MyDataValue = ...;
}

const Data MyData::MyData = MyDataValue;

// If you use MyData inside of the implementation file, it gets the conxtepr value.
void MyClass:foo() {
  // std::array<float, MyClass::Data.some_member> some_array; 
  std::array<float, MyDataValue.some_member> some_array; // no problem
}
// Some_other_file.cpp

int foo() {
  // That's fine, MyClass::Data will be resolved either at runtime or during lto.
  return MyClass::Data.some_member;
}

int bar() {
  std::array<float, MyClass::Data.some_member> some_array; // ERROR! Can't be resolved at compile time
}

那么在不立即初始化的情况下声明静态constexpr 数据成员有什么意义呢?

只要您稍后在 header 中定义它,然后再实际使用它,那么一切都很好,这有时会派上用场。但是,在您的情况下,由于您想在实现文件中定义变量,因此您创建了这个两层系统,其中成员在实现文件中为 constexpr,在其他任何地方为 const

所有这一切都是说:如果您所有延迟的constexpr 定义都在您发布的代码中的实现文件中,那么只需将它们设为const,并在本地使用constexpr 在实现文件内的匿名命名空间中如果你需要它。完全不需要宏。

【讨论】:

    猜你喜欢
    • 2016-10-15
    • 2013-01-10
    • 2018-11-11
    • 2012-07-16
    • 1970-01-01
    • 1970-01-01
    • 2021-08-07
    • 2013-10-19
    相关资源
    最近更新 更多