【问题标题】:C++ Singleton Creation ProblemC++ 单例创建问题
【发布时间】:2009-06-23 20:29:12
【问题描述】:

我最近获得了一些想要移植到 Linux 的代码。不过,在头文件中,我有一些奇怪的代码,希望有人能解释一下。在头文件中,在定义其他类的命名空间中,我有以下内容:

#define CREATE_SINGLETON_METHODS(s) \
  private: \
    friend class Singleton<c>; \
    ##c(); \
    virtual ~##c();

我知道##是token粘贴操作,但我不明白为什么原作者(我不认识,联系不上)使用它。我有一个如下所示的实现类:

class MapManager : public Singleton<MapManager> {
CREATE_SINGLETON_METHODS(MapManager)

private:
...

编译时出现以下错误:

error: pasting ";" and "MapManager" does not give a valid preprocessing token

这会在 Windows 和一些早期版本的 gcc(4.x 之前)上编译 find。关于这里可能发生什么的任何想法?谢谢!

【问题讨论】:

    标签: c++


    【解决方案1】:

    我使用自己的通用单例模板: 尝试用这个模板替换那些 icky 宏:

    #ifndef SINGLETON_HPP_STYLET
    #define SINGLETON_HPP_STYLET 0
    
    
    /*
    *
    *
    *   Generic Singleton implementation
    *
    *
    */ 
    
    
            template <class T>
            class SingletonHolder : public T
            {
                public:                                  
                    static SingletonHolder<T>& getInstance()
                    {
    
                        static SingletonHolder<T> instance;
                        return instance;
                    }
    
                private:
                    SingletonHolder()
                    {
                    };
    
                    virtual ~SingletonHolder()
                    {
                    };
    
            };//class SingletonHolder
    
    
    
    #endif //SINGLETON_HPP_STYLET
    

    //---------------------------------------- ---------------------------

    用法:

    类SomeClass;

    typedef SingletonHolder<SomeClass> SomeClassSingleton;
    
    
    SomeClassSingleton::getInstance().doSomething();
    

    【讨论】:

      【解决方案2】:

      在我看来,构造函数之前的## 没有用。析构函数上的那个是为了将波浪号粘贴到类型名上。可能是原作者复制粘贴,并没有出现编译错误,因为是老版本,所以没有捕捉到错误。

      【讨论】:

        【解决方案3】:

        我总是发现这种预处理器“魔法”(读取垃圾)造成的麻烦多于其价值。在某些情况下,这是一种必要的邪恶,但我认为这不适用于这里。我会在代码中重写它的任何实例并消除那个可怕的东西。

        【讨论】:

          【解决方案4】:

          尝试将宏更改为:

          #define CREATE_SINGLETON_METHODS(c) \
            private: \
              friend class Singleton<c>; \
              c(); // Note the change on this line! \
              virtual ~##c();
          

          我取出了## 运算符之一。好像 ”;”正在与“c();”合并在 gcc 4 中。

          【讨论】:

          • 这看起来只是预处理器之间的区别。
          • 正确 - 这就是重点。看起来 gcc4 的预处理器正在粘贴标记“Singleton;”以及“c();”的扩展当它看到## 运算符时。不过这个操作符是不必要的,所以简单的解决方法就是删除它。
          • 在析构函数上粘贴令牌也是错误的。析构函数使用标记“~”命名,然后是类名。它们不需要粘贴在一起,即使它们通常是在它们之间没有空格的情况下编写的。
          • @Doug:说得好,但我不认为这是“错误”,因为它会导致任何问题......只是没有必要。
          【解决方案5】:

          用于“字符串化”和标记粘贴的预处理器运算符(“#”和“##”)只能可靠地用于宏参数。此外,您通常需要使用一定程度的间接来让它们在所有情况下都能正常工作(尤其是在将它们与本身就是宏的东西一起使用时)。

          了解更多详情。

          【讨论】:

            【解决方案6】:

            前段时间我也遇到过类似的问题。尝试运行:

            g++ -E mytestfile.h

            (当然使用你自己的文件名)在命令行上查看预处理器的结果是什么。它可能有助于显示正在发生的事情。我正在运行 gcc 3.4.4 并遇到您描述的仅在 4.x 中发生的相同错误(尽管代码在 Visual Studio 2008 中运行良好)。

            消除标记器将解决问题:

            #define CREATE_SINGLETON_METHODS(c) \
            private: \
            friend class Singleton<c>; \
            c(); \
            virtual ~c();
            

            此代码为我提供了以下预处理结果(从我的命令行使用 -E 选项复制):

            private: friend class Singleton<MapManager>; MapManager(); virtual ~MapManager();
            

            编译器很满意。

            根据http://gcc.gnu.org/onlinedocs/gcc-4.0.4/cpp/Tokenization.html

            一旦输入文件被分解成标记,标记边界永远不会改变,除非使用 `##' 预处理运算符将标记粘贴在一起。 编译器不会重新标记预处理器的输出。每个预处理标记成为一个编译器标记。

            '##' 在这种情况下会破坏标记化规则。根据你得到的错误,预处理器很可能给编译器一个带有“;MapManager()”的标记集,4.x 编译器显然不喜欢。

            这里的一些信息表(http://gcc.gnu.org/onlinedocs/cpp/Concatenation.html#Concatenation)也值得注意:

            发现不必要的用途是很常见的 复杂宏中的“##”。如果你得到 这个警告,很可能你 可以简单地删除'##'。

            希望对您有所帮助。

            【讨论】:

              【解决方案7】:

              你到底是如何在任何编译器上编译的?首先应该是:

              #define CREATE_SINGLETON_METHODS(c) \
                private: \
                  friend class Singleton<c>; \
                  ##c(); \
                  virtual ~##c();
              

              因为我假设它是定义友元类的宏参数、一个私有构造函数和一个私有析构函数。

              【讨论】:

              • 是的,没错。当我输入我的问题时,我有一个错字,但其他一切都是正确的。我不知道它是如何编译的,但它可以在 Windows (Visual C++ 8.0) 上编译,没有任何抱怨。
              【解决方案8】:

              这看起来像是自动创建单例的失败尝试。一般来说,如果您可以通过一些巧妙的模板技巧来忍受虚函数或虚继承,则无需宏也可以做到这一点。

              如果我是你,我只会为受影响的类手动重写相关代码。对于这样的事情,宏是个坏消息。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2012-09-15
                • 2020-05-09
                • 2013-08-08
                • 1970-01-01
                相关资源
                最近更新 更多