C++中能否用临时对象指针初始化多态C数组?
问题
在C++中,是否可以使用临时对象的引用/指针来初始化多态C数组(例如类型为ParentClass*的数组,包含Subclass1*、Subclass2*等类型的指针)?由于所有临时对象在编译时都是已知的,我不想使用unique_ptr或任何涉及动态分配的方式(比如用new)。
用例
当前版本
现有代码如下:
class Command { public: virtual void run(const vector<string>& args) = 0; }; class Generate: public Command { public: void run(const vector<string>& args) override; }; class Scan: public Command { public: void run(const vector<string>& args) override; };
我希望定义一个包含static Command* commands[]数据成员的App类,示例如下:
// App.hpp class App { static Command* commands[]; }; // App.cpp Generate generate; Scan scan; Command* App::commands[] {&generate, &scan};
编译错误
在尝试寻找可行方案时,遇到以下编译错误:
taking the address of a temporary object of type 'Generate': Command* App::commands[] {&Generate()};
non-const lvalue reference to type 'Generate' cannot bind to a temporary of type 'Generate' Command* App::commands[] {&static_cast<Generate&>(Generate())};
cannot initialize an array element of type 'Command *' with an rvalue of type 'const Generate *' Command* App::commands[] {&static_cast<const Generate&>(Generate())};
编译器版本:g++: Apple clang version 15.0.0 (clang-1500.0.29.1)
期望改动
我希望在App.cpp中移除全局变量generate和scan,改用临时对象,例如:
// App.cpp Command* App::commands[] {&Generate(), &Scan()};
更理想的是移除App.cpp,直接在App.hpp中定义commands,例如:
// App.hpp class App { static Command* commands[] {&Generate(), &Scan()}; }; // App.cpp 被删除
解决方案
直接取临时对象的指针是未定义行为,C++标准明确禁止这种操作——临时对象的生命周期通常只持续到所在完整表达式结束,一旦表达式执行完毕,对象就会被销毁,留下的指针会变成悬空指针,后续访问会导致程序崩溃或不可预测的行为。
不过你可以通过以下几种合法方式实现需求,既避免全局变量,又无需动态分配:
静态局部对象封装
在数组初始化时调用返回对象引用的函数,函数内部用静态局部对象保证生命周期:// App.hpp class App { static Command& getGenerate() { static Generate instance; return instance; } static Command& getScan() { static Scan instance; return instance; } static Command* commands[] {&getGenerate(), &getScan()}; };静态局部对象的生命周期从第一次调用函数开始,直到程序结束,完全符合需求,且可直接在头文件中定义,无需cpp文件。
inline静态成员初始化(C++17及以上)
利用C++17的inline静态成员特性,直接在类内部初始化包含静态对象地址的数组:// App.hpp class App { inline static Generate generate; inline static Scan scan; inline static Command* commands[] {&generate, &scan}; };这种方式不需要单独的cpp文件,inline关键字确保静态成员在整个程序中只有一个实例,避免多重定义问题。
constexpr静态对象(若类构造函数支持)
如果Generate和Scan的构造函数是constexpr,可以用编译期静态对象初始化数组:// App.hpp class Command { public: constexpr Command() = default; virtual void run(const vector<string>& args) = 0; }; class Generate: public Command { public: constexpr Generate() = default; void run(const vector<string>& args) override; }; class Scan: public Command { public: constexpr Scan() = default; void run(const vector<string>& args) override; }; class App { inline static constexpr Generate generate{}; inline static constexpr Scan scan{}; inline static Command* commands[] {&generate, &scan}; };该方式无需cpp文件,对象在编译时完成初始化,契合你“编译时已知”的要求。
关键原因
数组是静态存储期对象,生命周期贯穿整个程序,但临时对象的生命周期最多到所在表达式结束。两者生命周期不匹配,即使通过强制转换获取临时对象地址,后续访问时对象已销毁,属于未定义行为,因此编译器直接禁止此类操作。
内容的提问来源于stack exchange,提问作者T. R. Bernstein

