Конструктор — запустить функцию перед инициализацией члена класса

У меня есть 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() выглядит как самое чистое решение.

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

17

Решение

Запятая оператора на помощь! Выражение (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;
};
14

Другие решения

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

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

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

Подход А:

Чистый путь.

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

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

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

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

1

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

1

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

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;
};
1

Если это принадлежит DeviceContext (и это похоже на ваш код), позвоните из DeviceContext c’tor.

0

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

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

Оператор запятой в вашем случае будет работать хорошо, но я думаю, что эта проблема является следствием плохого планирования ваших классов. Что бы я сделал — позволить конструкторам только инициализировать состояние объектов, а не их зависимости (например, контекст рендеринга 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();
}
0
По вопросам рекламы [email protected]