С++ - запуск функции перед инициализацией члена класса
У меня есть 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();
}