C++ - запустить функцию перед инициализацией члена класса

У меня есть 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. (стоит ли это делать, если у него есть ретвал?)
  • Используйте 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 InitDev() выглядит как самое чистое решение.

Всегда ли мне в таких случаях нужно решение на основе интеллектуальных указателей?


person Christian Aichinger    schedule 09.11.2012    source источник
comment
Это похоже на тот случай, когда статическая фабричная функция может быть более полезной, чем конструктор.   -  person cdhowie    schedule 09.11.2012
comment
мне кажется, что ваше третье решение сработает. Есть ли какая-то причина, по которой вы решили этого не делать? Кроме того, почему бы не использовать такую ​​библиотеку, как GLFW, для загрузки контекста openGL?   -  person Eric B    schedule 09.11.2012
comment
Я не знал о GLFW, когда начинал. Посмотрю, спасибо.   -  person Christian Aichinger    schedule 09.11.2012
comment
Другим решением является использование оператора запятой: stackoverflow.com/a/13314512/6210   -  person MSN    schedule 10.11.2012


Ответы (8)


Оператор запятой на помощь! Выражение (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;
};
person MSN    schedule 09.11.2012
comment
Что делать, если конструктор OpenGLContext принимает более 1 аргумента? - person IgNite; 23.01.2021

Всегда ли мне в таких случаях нужно решение на основе интеллектуальных указателей?

Нет. Избегайте этого ненужного осложнения.

Два непосредственных подхода, которые не были упомянуты:

Подход А:

Чистый путь.

Создайте небольшой объект-контейнер для хранилища 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;
};

Подход Б:

Быстрый и грязный способ. В этом случае вы можете использовать статическую функцию:

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;
};
person justin    schedule 09.11.2012

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

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

unique_ptr обычно используется, когда вы хотите сделать один из элементов необязательным или «обнуляемым», что вы можете или не хотите делать здесь.

person Dan    schedule 09.11.2012

Использование 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() на лямбду.

person stijn    schedule 09.11.2012

Во-первых, вы делаете это неправильно. :-) Очень плохая практика делать сложные вещи в конструкторах. Всегда. Сделайте эти операции функциями вспомогательного объекта, который вместо этого должен быть передан в конструктор. Лучше создавать свои сложные объекты вне вашего класса и передавать их полностью созданными, таким образом, если вам нужно передать их другим классам, вы можете сделать это в ИХ конструкторах одновременно. Кроме того, таким образом у вас есть шанс обнаружить ошибки, добавить разумное ведение журнала и т. д.

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;
};
person Jason H    schedule 10.11.2012

Если он принадлежит DeviceContext (и так кажется из вашего кода), вызовите его из DeviceContext c'tor.

person SomeWittyUsername    schedule 09.11.2012

Объедините оператор запятой с IIFE (Immediately-Invoked Function Expression), который позволяет вам определять переменные и другие сложные вещи, недоступные только с помощью оператора запятой:

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

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