С++ - запуск функции перед инициализацией члена класса

У меня есть 2 класса управления ресурсами DeviceContext и OpenGLContext, оба являются членами 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<OpenGLContext> m_opengl; вместо OpenGLContext m_opengl;.
    Затем инициализируйте как m_opengl(), вызовите SetPixelFormat() в теле c'tor и используйте m_opengl.reset(new OpenGLContext);
  • Вызов SetPixelFormat() из DeviceContext c'tor

Какое из этих решений является предпочтительным и почему? Что-нибудь мне не хватает?

Я использую Visual Studio 2010 Express в Windows, если это имеет значение.

Изменить: Меня больше интересуют компромиссы, связанные с решением одного из этих методов.

  • m_dummy() не работает и кажется неэлегантным, даже если он Мне интересен
  • unique_ptr<X> - когда я буду использовать его вместо "нормального" члена X m_x? Эти два метода кажутся функционально более или менее эквивалентными, за исключением проблем с инициализацией.
  • Вызов SetPixelFormat() от DeviceContext c'tor, безусловно, работает, но для меня он нечист. DeviceContext должен управлять ресурсом и активировать его использование, а не накладывать на пользователей какую-то политику случайных пикселей.
  • stijn's InitDev() выглядит как самое чистое решение.

В любом случае, я всегда предпочитаю решение на основе умного указателя в таких случаях?

Ответы

Ответ 1

Comma operator для спасения! В выражении (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;
};

Ответ 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

Использование uniqe_ptr для обоих кажется уместным здесь: вы можете перенаправить объявление DeviceContext и OpenGLContext, а не включать их заголовки, что хорошая вещь). Затем это работает:

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 ) )
{
}

Если вы можете использовать С++ 11, вы можете заменить InitDev() на лямбда.

Ответ 4

Если OpenGLContext имеет конструктор аргументов 0 и конструктор копирования, вы можете изменить свой конструктор на

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

unique_ptr обычно используется, если вы хотите сделать один из членов опционным или "nullable", который вы можете или не хотите делать здесь.

Ответ 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 operator с IIFE (Выражение с немедленной вызывной функцией) ), который позволяет вам определять переменные и другие сложные вещи, недоступные только с помощью оператора запятой:

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

Ответ 8

Оператор запятой в вашем случае будет очень хорошо, но я думаю, что эта проблема является следствием плохого планирования ваших классов. Я бы сделал, чтобы конструкторы только инициализировали состояние объектов, а не зависимостей (например, контекст рендеринга OpenGL). Я предполагаю, что конструктор OpenGLContext инициализирует контекст рендеринга OpenGL и что я не буду делать. Вместо этого я создам метод CreateRenderingContext для класса OpenGLContext для инициализации, а также для вызова 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();
}