【问题标题】:C++ - Run a function before initializing a class memberC++ - 在初始化类成员之前运行一个函数
【发布时间】:2012-11-09 15:43:08
【问题描述】:

我有 2 个资源管理类 DeviceContextOpenGLContext 都是 class DisplayOpenGL 的成员。资源生命周期与DisplayOpenGL 相关联。初始化如下(伪代码):

DeviceContext m_device = DeviceContext(hwnd);
m_device.SetPixelFormat();
OpenGLContext m_opengl = OpenGLContext(m_device);

问题在于对 SetPixelFormat() 的调用,因为我无法在 DisplayOpenGL c'tor 的初始化列表中执行此操作:

class DisplayOpenGL {
public:
    DisplayOpenGL(HWND hwnd)
    : m_device(hwnd),
      // <- Must call m_device.SetPixelFormat here ->
      m_opengl(m_device) { };
private:
    DeviceContext m_device;
    OpenGLContext m_opengl;
};

我能看到的解决方案:

  • 插入m_dummy(m_device.SetPixelFormat()) - 由于 SetPixelFormat() 没有 retval,因此无法正常工作。 (如果它有 retval,你应该这样做吗?)
  • 使用unique_ptr&lt;OpenGLContext&gt; m_opengl; 而不是OpenGLContext m_opengl;
    然后初始化为m_opengl(),在c'tor body中调用SetPixelFormat()并使用m_opengl.reset(new OpenGLContext);
  • DeviceContext c'tor 致电SetPixelFormat()

哪些解决方案更可取,为什么?我有什么遗漏吗?

如果重要的话,我在 Windows 上使用 Visual Studio 2010 Express。

编辑:我最感兴趣的是在决定其中一种方法时所涉及的权衡。

  • m_dummy() 不起作用,即使它会看起来也不优雅
  • unique_ptr&lt;X&gt; 对我来说很有趣——我什么时候可以使用它而不是“普通”X m_x 成员?除了初始化问题外,这两种方法在功能上似乎或多或少是等效的。
  • DeviceContext c'tor 调用SetPixelFormat() 确实有效,但我觉得不干净。 DeviceContext 应该管理资源并启用它的使用,而不是对用户强加一些随机像素格式策略。
  • stijn's InitDev() 看起来是最干净的解决方案。

在这种情况下,我是否总是需要基于智能指针的解决方案?

【问题讨论】:

  • 这似乎是静态工厂函数可能比构造函数更有用的情况。
  • 在我看来,您的第三个解决方案会起作用。你有什么理由选择不这样做吗?另外,为什么不使用 GLFW 之类的库来加载您的 OpenGL 上下文?
  • 刚开始的时候我并不知道 GLFW。看看,谢谢。
  • 另一种解决方案是使用逗号运算符:stackoverflow.com/a/13314512/6210

标签: c++ constructor initialization smart-pointers initialization-list


【解决方案1】:

Comma operator to the rescue! 表达式 (a, b) 将首先计算 a,然后是 b

class DisplayOpenGL {
public:
    DisplayOpenGL(HWND hwnd)
    : m_device(hwnd),
      m_opengl((m_device.SetPixelFormat(), m_device)) { };
private:
    DeviceContext m_device;
    OpenGLContext m_opengl;
};

【讨论】:

  • 如果OpenGLContext 构造函数接受多个参数怎么办?
  • 我想你会这样做m_opengl((m_device.SetPixelFormat(), m_device), arg2)
【解决方案2】:

在这种情况下,我是否总是需要基于智能指针的解决方案?

没有。避免这种不必要的复杂情况。

两种未提及的直接方法:

方法 A:

干净的方式。

m_device 的存储创建一个小容器对象,它在构造函数中调用SetPixelFormat()。然后将DisplayOpenGL ::m_device 替换为该类型的实例。得到了初始化顺序,意图很明确。插图:

class DisplayOpenGL {
public:
    DisplayOpenGL(HWND hwnd)
        : m_device(hwnd),
            m_opengl(m_device) { }
private:
    class t_DeviceContext {
    public:
        t_DeviceContext(HWND hwnd) : m_device(hwnd) {
            this->m_device.SetPixelFormat();
        }
        // ...
    private:
        DeviceContext m_device;
    };
private:
    t_DeviceContext m_device;
    OpenGLContext m_opengl;
};

方法 B:

快速而肮脏的方式。在这种情况下,您可以使用静态函数:

class DisplayOpenGL {
public:
    DisplayOpenGL(HWND hwnd)
    : m_device(hwnd),
      m_opengl(InitializeDevice(m_device)) { }
private:
    // document why it must happen this way here
    static DeviceContext& InitializeDevice(DeviceContext& pDevice) {
      pDevice.SetPixelFormat();
      return pDevice;
    }
private:
    DeviceContext m_device;
    OpenGLContext m_opengl;
};

【讨论】:

    【解决方案3】:

    如果OpenGLContext 有一个 0 参数构造函数和复制构造函数,你可以将你的构造函数更改为

    DisplayOpenGL(HWND hwnd)
    : m_device(hwnd)
    {
        m_device.SetPixelFormat();
        m_opengl = OpenGLContext(m_device);
    };
    

    unique_ptr 通常用于当您希望将成员之一设为可选或“可空”时,您可能希望或不希望在此处执行此操作。

    【讨论】:

      【解决方案4】:

      在这里使用 uniqe_ptr 似乎很合适:您可以转发声明 DeviceContext 和 OpenGLContext,而不是包括它们的标头,即 a good thing)。然后这个工作:

      class DisplayOpenGL
      {
      public:
        DisplayOpenGL( HWND h );
      private:
        unique_ptr<DeviceContext> m_device;
        unique_ptr<OpenGLContext> m_opengl;
      };
      
      namespace
      {
        DeviceContext* InitDev( HWND h )
        {
          DeviceContext* p = new DeviceContext( h );
          p->SetPixelFormat();
          return p;
        }
      }
      
      DisplayOpenGL::DisplayOpenGL( HWND h ):
        m_device( InitDev( h ) ),
        m_opengl( new OpenGLContext( *m_device ) )
      {
      }
      

      如果您可以使用 c++11,您可以将 InitDev() 替换为 lambda。

      【讨论】:

        【解决方案5】:

        首先,你做错了。 :-) 在构造函数中做复杂的事情是非常糟糕的做法。曾经。在必须传递给构造函数的辅助对象上创建这些操作函数。更好的是在你的类之外构造你的复杂对象并将它们完全创建,这样如果你需要将它们传递给其他类,你也可以同时将它们传递给它们的构造函数。此外,这样您就有机会检测错误、添加合理的日志记录等。

        class OpenGLInitialization
        {
        public:
            OpenGLInitialization(HWND hwnd)
                : mDevice(hwnd) {}
            void                 SetPixelFormat  (void)       { mDevice.SetPixelFormat(); }
            DeviceContext const &GetDeviceContext(void) const { return mDevice; }
        private:
            DeviceContext mDevice;
        };        
        
        class DisplayOpenGL 
        {
        public:
            DisplayOpenGL(OpenGLInitialization const &ogli)
            : mOGLI(ogli),
              mOpenGL(ogli.GetDeviceContext())
              {}
        private:
            OpenGLInitialization mOGLI;
            OpenGLContext mOpenGL;
        };
        

        【讨论】:

        【解决方案6】:

        如果它属于DeviceContext(从您的代码看来如此),请从DeviceContext c'tor 调用它。

        【讨论】:

          【解决方案7】:

          Comma operatorIIFE (Immediately-Invoked Function Expression) 组合在一起,这样您就可以定义仅使用逗号运算符不可用的变量和其他复杂内容:

          struct DisplayOpenGL {
              DisplayOpenGL(HWND hwnd)
                  : m_device(hwnd)
                  , opengl(([&] {
                      m_device.SetPixelFormat();
                  }(), m_device))
              DeviceContext m_device;
              OpenGLContext m_opengl;
          };
          

          【讨论】:

            【解决方案8】:

            逗号运算符在您的情况下会做得很好,但我认为这个问题是您的课程计划不当造成的。我要做的是让构造函数只初始化对象的状态而不是依赖项(例如 OpenGL 渲染上下文)。我假设 OpenGLContext 的构造函数初始化了 OpenGL 渲染上下文,这就是我不会做的。相反,我会为 OpenGLContext 类创建方法CreateRenderingContext 来进行初始化并调用SetPixelFormat

            class OpenGLContext {
            public:
                OpenGLContext(DeviceContext* deviceContext) : m_device(deviceContext) {}
                void CreateRenderingContext() {
                    m_device->SetPixelFormat();
                    // Create the rendering context here ...
                }
            private: 
                DeviceContext* m_device;
            };
            
            ...
            
            DisplayOpenGL(HWND hwnd) : m_device(hwnd), m_opengl(&m_device) {
                m_opengl.CreateRenderingContext();
            }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多