У меня есть 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 () не имеет обратного хода. (Вы должны сделать это, если у него был ретваль?)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
не может, конечно, работает, но кажется мне нечистым. DeviceContext
должен управлять ресурсом и разрешать его использование, а не навязывать пользователям какую-либо политику формата случайных пикселей.InitDev()
выглядит как самое чистое решение.В любом случае, всегда ли мне нужно решение на основе умного указателя в таких случаях?
Запятая оператора на помощь! Выражение (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;
};
В любом случае, всегда ли мне нужно решение на основе умного указателя в таких случаях?
Нет. Избегайте этого ненужного осложнения.
Два непосредственных подхода, которые не были упомянуты:
Подход А:
Чистый путь.
Создайте маленький контейнерный объект для 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;
};
Если OpenGLContext
имеет конструктор аргумента 0 и конструктор копирования, который вы можете изменить на конструктор
DisplayOpenGL(HWND hwnd)
: m_device(hwnd)
{
m_device.SetPixelFormat();
m_opengl = OpenGLContext(m_device);
};
unique_ptr
обычно используется, когда вы хотите сделать одного из участников необязательным или «обнуляемым», что вы можете или не можете делать здесь.
Использование 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 ) )
{
}
Если вы можете использовать c ++ 11, вы можете заменить InitDev () на лямбду.
Прежде всего, вы делаете это неправильно. 🙂 Это очень плохая практика делать сложные вещи в конструкторах. Когда-либо. Сделайте эти операции функциями для вспомогательного объекта, который вместо этого должен быть передан в конструктор. Лучше создать свои сложные объекты вне вашего класса и передать их полностью созданными. Таким образом, если вам нужно передать их другим классам, вы можете сделать это и в конструкторы ИХ одновременно. Кроме того, у вас есть возможность обнаруживать ошибки, добавлять логичные записи и т. Д.
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;
};
Если это принадлежит DeviceContext
(и это похоже на ваш код), позвоните из DeviceContext
c’tor.
Объединить Оператор запятой с IIFE (выражение функции с немедленным вызовом), который позволяет вам определять переменные и другие сложные вещи, недоступные только с помощью оператора запятой:
struct DisplayOpenGL {
DisplayOpenGL(HWND hwnd)
: m_device(hwnd)
, opengl(([&] {
m_device.SetPixelFormat();
}(), m_device))
DeviceContext m_device;
OpenGLContext m_opengl;
};
Оператор запятой в вашем случае будет работать хорошо, но я думаю, что эта проблема является следствием плохого планирования ваших классов. Что бы я сделал — позволить конструкторам только инициализировать состояние объектов, а не их зависимости (например, контекст рендеринга 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();
}