【问题标题】:Right way to conditionally initialize a C++ member variable?有条件地初始化 C++ 成员变量的正确方法?
【发布时间】:2009-06-18 18:52:00
【问题描述】:

我确信这是一个非常简单的问题。以下代码显示了我正在尝试做的事情:

class MemberClass {
public:
    MemberClass(int abc){ }
};

class MyClass {
public:
    MemberClass m_class;
    MyClass(int xyz) {
        if(xyz == 42)
            m_class = MemberClass(12);
        else
            m_class = MemberClass(32);
    }
};

这不会编译,因为m_class 是使用空构造函数(不存在)创建的。这样做的正确方法是什么?我的猜测是使用指针并使用new 实例化m_class,但我希望有更简单的方法。

编辑:我应该早点说,但我的实际问题还有一个复杂的问题:我需要在初始化 m_class 之前调用一个方法,以便设置环境。所以:

class MyClass {
public:
    MemberClass m_class;
    MyClass(int xyz) {
        do_something(); // this must happen before m_class is created
        if(xyz == 42)
            m_class = MemberClass(12);
        else
            m_class = MemberClass(32);
    }
};

是否有可能通过花哨的初始化列表技巧来实现这一点?

【问题讨论】:

    标签: c++ class member-initialization


    【解决方案1】:

    使用条件运算符。如果表达式较大,请使用函数

    class MyClass {
    public:
        MemberClass m_class;
        MyClass(int xyz) : m_class(xyz == 42 ? 12 : 32) {
    
        }
    };
    
    class MyClass {
        static int classInit(int n) { ... }
    public:
        MemberClass m_class;
        MyClass(int xyz) : m_class(classInit(xyz)) {
    
        }
    };
    

    要在初始化 m_class 之前调用函数,您可以在该成员之前放置一个结构并利用 RAII

    class MyClass {
        static int classInit(int n) { ... }
        struct EnvironmentInitializer {
            EnvironmentInitializer() {
                do_something();
            }
        } env_initializer;
    public:
        MemberClass m_class;
        MyClass(int xyz) : m_class(classInit(xyz)) {
    
        }
    };
    

    这将在初始化m_class 之前调用do_something()。请注意,在构造函数初始值设定项列表完成之前,不允许调用 MyClass 的非静态成员函数。该函数必须是其基类的成员,并且必须已经完成基类的ctor才能使其工作。

    还要注意,当然,对于创建的每个单独的对象,总是调用该函数 - 不仅是为创建的第一个对象。如果你想这样做,你可以在初始化器的构造函数中创建一个静态变量:

    class MyClass {
        static int classInit(int n) { ... }
        struct EnvironmentInitializer {
            EnvironmentInitializer() {
                static int only_once = (do_something(), 0);
            }
        } env_initializer;
    public:
        MemberClass m_class;
        MyClass(int xyz) : m_class(classInit(xyz)) {
    
        }
    };
    

    它使用逗号运算符。请注意,您可以使用函数尝试块捕获do_something 引发的任何异常

    class MyClass {
        static int classInit(int n) { ... }
        struct EnvironmentInitializer {
            EnvironmentInitializer() {
                static int only_once = (do_something(), 0);
            }
        } env_initializer;
    public:
        MemberClass m_class;
        MyClass(int xyz) try : m_class(classInit(xyz)) {
    
        } catch(...) { /* handle exception */ }
    };
    

    如果do_something函数抛出导致MyClass对象创建失败的异常,下次会再次调用。希望这会有所帮助:)

    【讨论】:

    • 谢谢!我已经更新了我的问题 - 我实际上需要在创建 m_class 之前运行一个方法。那可能吗?也许我可以在“classInit”中这样做,但这不会很优雅。
    • 当我看到逗号运算符的使用时,我知道回答这个问题的人很聪明。当我看到函数 try 块的使用时,我知道回答这个问题的人是 litb。当我看到回答这个问题的人是 litb... 好吧我想我已经知道是他了 :)
    • 如果变量的构造函数采用多个参数,它也会变得更加复杂;那么每个参数都需要这些函数之一
    • +1 用于简洁地推荐聪明的东西,而不是像其他劣质答案那样动态/基于指针的初始化。 C++ 具有 RVO 和三元运算符之类的东西意味着我们不必在这种情况下使用 new/pointers 放屁。我认为我们的口头禅是不使用动态分配,除非我们有充分的理由?这不是一个。
    【解决方案2】:

    使用初始化列表语法:

    class MyClass {
    public:
        MemberClass m_class;
        MyClass(int xyz) : m_class(xyz == 42 ? MemberClass(12) : MemberClass(32)
                                   /* see the comments, cleaner as xyz == 42 ? 12 : 32*/)
        { }
    };
    

    工厂可能更清洁:

    MemberClass create_member(int x){
       if(xyz == 42)
         return MemberClass(12);
        // ...
    }
    
    //...
     MyClass(int xyz) : m_class(create_member(xyz))
    

    【讨论】:

    • 你正在创建一个未命名的对象,然后使用 copy-ctor 来初始化成员,而不是直接初始化它。
    • 是的,我打字太快了。就个人而言,我仍然会将逻辑移动到 MemberClass 或工厂中。
    【解决方案3】:
     MyClass(int xyz) : m_class(xyz==42 ? 12 : 32) {}
    

    要回答您修改后的问题,这有点棘手。最简单的方法是使m_class 成为一个指针。如果你真的想要它作为数据成员,那么你必须要有创意。创建一个新类(最好在 MyClass 内部定义它)。让它的 ctor 成为需要调用的函数。将它first包含在数据成员的声明中(这将使它成为第一个实例化的对象)。

    class MyClass 
    {
         class initer { public: initer() {
                        // this must happen before m_class is created
                        do_something();                        
                        }
                       }
    
        initer     dummy;
    public:
    
        MemberClass m_class;
        MyClass(int xyz) : m_class(xyz==42? 12 : 43)
        {
            // dummy silently default ctor'ed before m_class.
        }
    };
    

    【讨论】:

      【解决方案4】:

      或者:

      class MemberClass {
      public:
          MemberClass(int abc){ }
      };
      
      class MyClass {
      public:
          MemberClass* m_class;
          MyClass(int xyz) {
              if(xyz == 42)
                  m_class = new MemberClass(12);
              else
                  m_class = new MemberClass(32);
          }
      };
      

      如果您仍然想保持相同的语法。不过,成员初始化更有效。

      【讨论】:

        【解决方案5】:

        试试这个:

        class MemberClass
        {
        public:    
           MemberClass(int abc = 0){ }
        };
        

        这给了它一个默认值,以及你的默认构造函数。

        【讨论】:

        • 有时没有默认构造函数是有原因的。
        • 你必须有一个默认参数,让它进入一些已知的僵尸状态。不理想,但有时是唯一的方法。
        • 有时将默认构造函数设为私有是有原因的,但它应该始终存在...
        【解决方案6】:

        要在其他事情发生后进行初始化,您确实需要使用指针,如下所示:

        class MyClass {
        public:
            MemberClass * m_pClass;
            MyClass(int xyz) {
                do_something(); // this must happen before m_class is created
                if(xyz == 42)
                    m_pClass = new MemberClass(12);
                else
                    m_pClass = new MemberClass(32);
            }
        };
        

        唯一的区别是您需要在析构函数中以m_pClass->counter 而不是m_class.counterdelete m_pClass 的形式访问成员变量。

        【讨论】:

        • 另一种方法是将 Myclass 初始化为某种虚拟僵尸状态,然后在知道值后重新初始化。但我更喜欢指针。
        猜你喜欢
        • 1970-01-01
        • 2018-11-24
        • 1970-01-01
        • 2012-08-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多