我正在尝试获得一个演示代码,以在 ODR 使用的帮助下显示自动注册(我在最后一天才了解到这一点,所以如果我没有正确使用它,请原谅我)。
这是代码中有趣的部分
template <const bool*>
struct OdrUse {};
bool Register(const char *) {return true;}
template <typename T>
struct Registered {
static bool registredInInit; // If 'const' is added, clang does not accept the code as well.
static constexpr OdrUse<®istredInInit> odrUser{};
};
template<typename T> // If defined after 'Class', both gcc and clang accept the code.
bool Registered<T>::registredInInit = Register(T::NAME);
struct Class : Registered<Class> {
static constexpr const char* NAME = "ZIP";
};
int main() {
}
很多代码已经复制自https://www.cppstories.com/2018/02/factory-selfregister/
我在编译时遇到这个错误
$ g++ test_odr.cc
test_odr.cc: In instantiation of ‘bool RegisteredInFactory<ZipCompression>::s_bRegistered’:
test_odr.cc:50:27: required from ‘class RegisteredInFactory<ZipCompression>’
test_odr.cc:59:31: required from here
test_odr.cc:55:44: error: incomplete type ‘ZipCompression’ used in nested name specifier
55 | (CompressionMethodFactory::Register(T::kFactoryName, T::CreateMethod),
| ^~~~~~~~~~~~
test_odr.cc:55:61: error: incomplete type ‘ZipCompression’ used in nested name specifier
55 | (CompressionMethodFactory::Register(T::kFactoryName, T::CreateMethod),
| ^~~~~~~~~~~~
看报错,为什么编译器需要
RegisteredInFactory<ZipCompression>
完全定义才能访问kFactoryName
?
修复似乎仅仅是:
#include <iostream>
#include <map>
#include <memory>
#include <string>
// Interface for the class.
class ICompressionMethod {
public:
ICompressionMethod() = default;
virtual ~ICompressionMethod() = default;
virtual void Compress() = 0;
};
// Helper class holding static methods for factory
class CompressionMethodFactory {
public:
using TCreateMethod = std::unique_ptr<ICompressionMethod> (*)();
public:
CompressionMethodFactory() = delete;
static bool Register(const std::string name, TCreateMethod createFunc) {
if (auto it = s_methods.find(name); it == s_methods.end()) {
s_methods[name] = createFunc;
std::cout << name << " registered\n";
return true;
}
return false;
}
static std::unique_ptr<ICompressionMethod> Create(const std::string& name) {
if (auto it = s_methods.find(name); it != s_methods.end())
return it->second();
return nullptr;
}
private:
static std::map<std::string, TCreateMethod> s_methods;
};
std::map<std::string, CompressionMethodFactory::TCreateMethod>
CompressionMethodFactory::s_methods;
template <bool*>
struct OdrUse {};
template <typename T>
class RegisteredInFactory {
protected:
static bool s_bRegistered;
static constexpr OdrUse<&s_bRegistered> odr{};
};
class ZipCompression : public ICompressionMethod,
public RegisteredInFactory<ZipCompression> {
public:
static constexpr const char* kFactoryName = "ZIP";
void Compress() override {
// if (s_bRegistered) std::cout << "registered\n";
std::cout << "in compress\n";
}
static std::unique_ptr<ICompressionMethod> CreateMethod() {
return std::make_unique<ZipCompression>();
}
static std::string GetFactoryName() { return "ZIP"; }
};
template <typename T>
bool RegisteredInFactory<T>::s_bRegistered =
(CompressionMethodFactory::Register(T::kFactoryName, T::CreateMethod),
true);
int main(int argc, char* argv[]) {
std::cout << "main starts...\n";
auto pMethod = CompressionMethodFactory::Create("ZIP");
if (pMethod != nullptr) {
pMethod->Compress();
} else {
std::cout << "factory creation failed\n";
}
std::cout << "end"
<< "\n";
return 0;
}
s_bRegistered
必须在声明 ZipCompression
之后进行初始化,这样 gcc 才能看到 ZipCompression
成员。但我不明白为什么这是一项要求?
我承认仍然需要更详细的答案。尝试用较小的示例重现问题可能会更容易。
也许那里有一些答案:
c++模板代码顺序解析/CRTP
一个假设:
class ZipCompression : public ICompressionMethod,
public RegisteredInFactory<ZipCompression>
此时 ZipCompression 仅被声明,并且仍然是一个不完整的类型,正如 gcc 正确所说的那样。它还会触发
RegisteredInFactory<ZipCompression>
的实例化,这反过来又需要初始化其静态成员,调用 Register(T::kFactoryName, T::CreateMethod)
但此时 T 仍然不完整。因此 gcc 是正确的,clang 和 msvc 是错误的(我觉得很奇怪)。
通常,当您在定义后从继承类调用基本成员函数时,CRTP 会起作用。所以这些函数只有在继承的类是一个完整的类之后才会被实例化。
我错了吗?
[编辑]
另一种修复方法是让
ZipCompression
触发初始化。这是在@yeputons 示例中实现的解决方案,更具可读性:
template <const bool*>
struct OdrUse {};
void Register(const char*) {}
template <typename T>
struct Registered {
static bool registredInInit; // If 'const' is added, clang does not accept
// the code as well.
static constexpr OdrUse<®istredInInit> odrUser{};
};
struct Class : Registered<Class> {
static constexpr const char* NAME = "ZIP";
static bool LocalRegister;
static bool Register() {
Registered<Class>::registredInInit = (::Register(NAME), true);
return true;
}
};
bool Class::LocalRegister = Register();
int main() {}