使用 C++ ODR 自动注册类型

问题描述 投票:0回答:1

我正在尝试获得一个演示代码,以在 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<&registredInInit> 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

c++ c++11 crtp one-definition-rule
1个回答
1
投票

修复似乎仅仅是:

#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<&registredInInit> 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() {}

现场演示

© www.soinside.com 2019 - 2024. All rights reserved.