LINUX.ORG.RU

PIMPL без указателя

 


1

8

Всем хорошо известен паттерн PIMPL через скрытие реализации за указателем. С известными же проблемами неоптимального исполнения, когда нужно дополнительно аллоцировать память под сам указатель. Сходу нашёл в интернете варианты вместо указателя использовать заранее большой массив, на котором вызывается placement new, но такой подход вызывает ещё больше вопросов.

В самом C++ для скрытия реализации есть абстрактные интерфейсы без данных, реализация которых создаётся через фабрику или статический метод вместо конструктора. Однако с абстрактными интерфейсами одна беда — если наследовать один интерфейс от другого, то их реализации будут иметь ромбовидное наследование — один раз от наследуемого интерфейса, второй раз от реализации базового интерфейса. Ложку дёгтя добавляет необходимость замены конструкторов фабриками и руками делать правильные вызовы родительских конструкторов. Добавим сюда необходимость писать для каждого класса фактически два: один обстрактный, второй с реализацией. Получим достаточно неудобный подход, недостатки которого устраняет PIMPL.

Возьмём к примеру библиотеку Qt, объектная модель которой полностью построена на PIMPL и наследовании от базового QObject. При этом сами объекты на стеке или по значению создаются крайне редко, в повальном случае экземпляры QObject выделяются в куче. Получаем двойную аллокацию: первую для самого указателя, вторую для данных по указателю. Также при использовании подобных экземпляров будет двойное разименование, что также негативно скажется на производительности. При этом подсознательно понимаешь, что подобный подход бессмысленнен, ведь PIMPL-указатель никогда не подменяет реализацию на этапе выполнения, он создаётся исключительно в конструкторе и удаляется исключительно в деструкторе объекта.

А что если вообще без указателя и данных в интерфейсе? Вместо дополнительного указателя на данные использовать указатель на интерфейс. Аллокацию же переопределять операторами new и delete.

Вот простой пример, нарисованный на коленке:

#pragma once

#include <cstddef>

class A
{
public:
	A(int value = 42);

	int value() const;
	void setValue(int value);

	DECLARE_PRIVATE(A)
};

class B : public A
{
public:
	B(int data);

	int data() const;

	DECLARE_PRIVATE(B)
};
#include "lib.h"

class A::Private
{
public:
	int value;
};
DEFINE_PRIVATE(A)

A::A(const int value)
{
	to_private()->value = value;
}

int A::value() const
{
	return to_private()->value;
}

void A::setValue(const int value)
{
	to_private()->value = value;
}


class B::Private : public A::Private
{
public:
	int data;
};
DEFINE_PRIVATE(B)

B::B(const int data)
{
	to_private()->data = data;
}

int B::data() const
{
	return to_private()->data;
}

DECLARE_PRIVATE и DEFINE_PRIVATE переопределяют операторы new и delete и определяют методы приведения реализации к интерфейсу:

#define DECLARE_PRIVATE(T) \
public: \
	void * operator new(std::size_t size); \
	void operator delete(void * ptr); \
	class Private; \
private: \
	const Private * to_private() const { return reinterpret_cast<const Private*>(this); } \
	Private * to_private() { return reinterpret_cast<Private*>(this); }

#define DEFINE_PRIVATE(T) \
	void * T::operator new(const std::size_t size) \
	{ \
		return new Private; \
	} \
	\
	void T::operator delete(void * const ptr) \
	{ \
		delete static_cast<Private*>(ptr); \
	}

Ограничения данного подхода очевидны: объекты нельзя создавать на стеке или в виде переменной-члена класса. Аллоцировать их можно только через new, к примеру:

std::unique_ptr<B> b(new B(95));
b->setValue(33);

Сравним с традиционным подходом в Qt:

  • Объявление класса: Qt — Q_DECLARE_PRIVATE(classname), в нашем случае то же самое — DECLARE_PRIVATE(classname).
  • Определение класса: Qt — наследование реализации, то же самое у нас + DEFINE_PRIVATE(classname).
  • Аллокация класса: Qt — явный вызов базового класса с передачей указателя на реализацию
    MyObject::MyObject() : MyParentObject(*new MyObjectPrivate)
    у нас — неявный вызов базового класса без лишних телодвижений.
  • Обращение к реализации: Qt — d_func(), у нас — to_private().
  • Аллокация объекта в куче: Qt — две аллокации, в нашем случае — одна.
  • Аллокация объекта на стеке: Qt — одна аллокация, в нашем случае тоже одна, через явный operator new.

Осталось только понять как запретить создавать объекты на стеке на этапе компиляции, но это уже детали.

Я догадываюсь, что всё выше есть велосипед, но почему-то я нигде не встречал подобной реализации (кроме исходников Андроида). Собственно, какие у есть минусы у данного подхода? Какие известные проекты практикуют подобный подход вместо традиционного PIMPL?

★★★★★

Последнее исправление: Dendy (всего исправлений: 4)

Залогировал сколько создаётся/удаляется экземпляров QObject в разных программах сразу после старта:

  • Qt Assistant: alloc=1678 free=981
  • Qt Designer: alloc=1995 free=898
  • Examples/Wiggly Widget: alloc=77 free=17
  • Examples/Main Window: alloc=580 free=61
  • Examples/QML Video: alloc=509 free=39
  • Examples/Same Game: alloc=5119 free=55

Как видим, даже в простых программах расходы на PIMPL огромны. Их можно не заметить на современных десктопных процессорах, но в embedded подобная роскошь выливается в сильно заметные тормоза на старте программы и фрагментацию памяти.

Dendy ★★★★★
() автор топика
Ответ на: комментарий от Dendy

Фишка pimpl еще в бинарной совместимости.

Ну а выделение объектов не в куче, а в какой-нибудь арене, сразу приведёт нас к необходимости следить за атомарностью операций и проверками пустых ячеек, от уже удалённых объектов (ака свой, примитивный gc). И насколько это будет быстрее - большой вопрос. Но старт - да, скорее всего будет быстрее.

RazrFalcon ★★★★★
()

Собственно, какие у есть минусы у данного подхода?

Ну например сделай метод A::setValue виртуальным и наслаждайся сегфолтами.

Происходит это потому, что компилятор добавляет скрытый указатель vptr в любые классы, содержащие виртуальные методы. В класс A этот указатель добавляется, а в класс A::Private - нет. Получается, что A::vptr располагается по тому же самому адресу, что и A::Private::value. Любая запись в A::Private::value затирает vptr и привет сегфолт.

Вот этот каст - UB по стандарту:

Private * to_private() { return reinterpret_cast<Private*>(this); }
Стандарт не гарантирует, что два любых независимых класса X и X::Private будут иметь одинаковый memory layout, включая скрытые поля, добавляемые компилятором. На практике этого можно добиться с конкретными компиляторами, но полагаться на UB при проектировании больших проектов - это плохая идея.

Ограничения данного подхода очевидны: объекты нельзя создавать на стеке или в виде переменной-члена класса.

Это серьезное ограничение. Что делать с разными pimpl-контейнерами вроде QString или QList, которые обычно создаются именно на стеке в количество over 9000 штук? Вкупе с ограничением на виртуальные методы оно делает твой метод неюзабельным.

archie
()
Ответ на: комментарий от RazrFalcon

Фишка pimpl еще в бинарной совместимости.

В моём случае та же бинарная совместимость. Разница с Qt только в том, что у них наружу торчит opaque-указатель на QObjectPrivate, а у меня нет. Под капотом реализации всё так же наследуются от базового Private-класса.

Ну а выделение объектов не в куче

В моём случае объекты так же выделяются в обычной куче. Разница только в том, что выделяется один, а не два, как в Qt.

Dendy ★★★★★
() автор топика
Ответ на: комментарий от archie

Что делать с разными pimpl-контейнерами вроде QString или QList

К слову о контейнерах, они вообще ничего не создают изначально. Qt использует паттерн «shared null» для создания «пустых» контейнеров. При первом изменении уже будет аллокация.

RazrFalcon ★★★★★
()

Кстати, товарищи из Qt сами в курсе подобных накладных расходов на PIMPL:

https://wiki.qt.io/D-Pointer

In the above code, creating a single Label results in the memory allocation for LabelPrivate and WidgetPrivate. If we were to employ this strategy for Qt, the situation becomes quite worse for classes like QListWidget - it is 6 levels deep in the class inheritance hierarchy and it would result in upto 6 memory allocations!

В примере по ссылке они показывают как сократить количество аллокаций с 6 до 1 путём наследования Private-классов друг от друга. Я же предлагаю пойти ещё дальше и сократить количество аллокаций до 0.

Dendy ★★★★★
() автор топика
Ответ на: комментарий от RazrFalcon

С контейнерами всё-таки другая история, они не предполагают наследование и реализуют CoW путём атомарной подмены указателя. В случае же с QObject данные никогда не переаллоцируются и я предлагаю оптимизировать именно этот случай. Хотя и с контейнерами я видел реализации без дополнительной аллокации, кажется в том же Андроиде.

Dendy ★★★★★
() автор топика
Ответ на: комментарий от archie

Ну например сделай метод A::setValue виртуальным и наслаждайся сегфолтами.

Отличный пример! Серьёзно. Я уже осознал это ограничение. К счастью оно легко обходится аллокацией в перегруженном операторе new отдельной области для аллоцируемого класса (её размер передаётся первым аргументом) и приватных данных (их размер определяется через sizeof), а также запоминанием смещения для корректного кастирования внутри to_private/from_private.

template <typename T>
void * alloc(std::size_t size)
{
	typedef typename T::Private Private;
	constexpr std::size_t sizeo = sizeof(size_t);

	size = std::max(sizeo, size);

	uint8_t * const data = static_cast<uint8_t*>(std::malloc(sizeo + size + sizeo + sizeof(Private)));
	reinterpret_cast<size_t&>(*data) = size;
	reinterpret_cast<size_t&>(*(data + sizeo + size)) = size;
	Private * p = new (data + sizeo + size + sizeo) Private;
	return data + sizeo;
}

template <typename T>
void free(void * const ptr)
{
	typedef typename T::Private Private;
	constexpr std::size_t sizeo = sizeof(size_t);

	uint8_t * const data = static_cast<uint8_t*>(ptr) - sizeo;
	reinterpret_cast<Private*>(data + sizeo + reinterpret_cast<const size_t&>(*data) + sizeo)->~Private();
	std::free(data);
}

template <typename T>
static const typename T::Private & to_private(const T & t)
{
	typedef typename T::Private Private;
	constexpr std::size_t sizeo = sizeof(size_t);

	const std::size_t size = reinterpret_cast<const std::size_t&>(*(reinterpret_cast<const uint8_t*>(&t) - sizeo));
	return reinterpret_cast<const Private&>(*(reinterpret_cast<const uint8_t*>(&t) + size + sizeo));
}

Структура аллоцируемой памяти такая: <размер класса> + <память класса> + <размер класса> + <памяти приватных данных класса>. Размер класса запоминается два раза, чтобы по указателю на класс можно было легко найти его приватные данные и наоборот, по приватным данным найти класс.

Dendy ★★★★★
() автор топика

Выложил исходники на github: https://github.com/dendy/zeropimpl

Проверил, что нормально работает и с наследованием от промежуточного класса, когда изменяется только таблица виртуальных функций и добавляются поля, но при этом приватные данные не меняются. То-есть типичный случай, когда в своей программе ты наследуешься от некого виджета

class MyWidget : public QWidget { QString someString; };
но при этом аллокацией самого класса и приватных данных занимается базовый класс QWidget.

Dendy ★★★★★
() автор топика
Ответ на: комментарий от Dendy

Да, теперь оно выглядит более-менее неплохо, но реализация to_private() существенно усложнилась. Получается, что за единичную экономию на одной аллокации мы платим перманентными тормозами при вызове любых методов на протяжении всей жизни приложения. Стоит ли оно того? Вряд ли.

archie
()
Ответ на: комментарий от annulen

А если объект создается не через new, на стэке например, все развалится?

Да, всё развалится, поэтому хотелось бы на этапе компиляции это запрещать. Пока не могу найти как, ограничился protected-конструктором в базовом классе, аргументы которому форвардятся через статический метод make(). Но отнаследованный класс может снять это ограничение.

Dendy ★★★★★
() автор топика
Ответ на: комментарий от archie

Получается, что за единичную экономию на одной аллокации мы платим перманентными тормозами при вызове любых методов на протяжении всей жизни приложения. Стоит ли оно того?

Давайте сравним с реализацией Qt. Внутри каждого (!) метода они сначала разименовывают указатель на приватные данные и сохраняют его в локальной переменной. В моём же случае указатель вычисляется прибавлением к this смещения. Да, сейчас указатель this так же разименовывается, чтобы посчитать смещение до Private. Но если поменять местами области памяти вот так: <размер класса> <приватные данные> <данные класса>, то смещение до приватных данных внутри to_private() вычисляется на этапе компиляции через sizeof(Private). То-есть оно уже должно быть быстрее чем в Qt.

Dendy ★★★★★
() автор топика
Последнее исправление: Dendy (всего исправлений: 1)
Ответ на: комментарий от Dendy

Да, всё развалится, поэтому хотелось бы на этапе компиляции это запрещать.

По-моему это, наоборот, делает всю конструкцию бесполезной

annulen ★★★★★
()
Ответ на: комментарий от Dendy

То-есть оно уже должно быть быстрее чем в Qt.

Можете показать асм обоих вариантов? А то гадать можно долго.

Вы планируете эту идею показать разрабам Qt или для себя?

RazrFalcon ★★★★★
()
Ответ на: комментарий от archie

Кстати, на счёт контейнеров. Как уже сказал выше с ними отдельная беда, потому что данные контейнера мутабельны, при этом меняются как сами данные, так и указатель на них при попытке изменения. Поэтому для каждого контейнера вопрос оптимизации решается по-своему.

К примеру, данные вектора располагаются в цельном куске памяти, почему бы этот кусок не соединить с метаданными вектора (количество элементов, размер ячейки, счётчик ссылок), отделив их смещением вместо двух отдельных аллокаций, как в Qt? Почему бы и нет, подумали разработчики Андроида и так и сделали:

https://android.googlesource.com/platform/system/core/ /refs/heads/marshmallo...

https://android.googlesource.com/platform/system/core/ /refs/heads/marshmallo...

Обратите внимание как они вычисляют указатель на данные по смещению:

const void* SharedBuffer::data() const {
    return this + 1;
}
Dendy ★★★★★
() автор топика
Ответ на: комментарий от Dendy

но в embedded подобная роскошь выливается в сильно заметные тормоза на старте программы и фрагментацию памяти.

Если мы об embedded, то из приведённой тобой статистики имеет смысл только QML Video. Если ты хочешь остальное запускать на embedded, то ты упоролсо.

UVV ★★★★★
()
Ответ на: комментарий от annulen

По-моему это, наоборот, делает всю конструкцию бесполезной

На стеке объекты конечно же можно выделять, только через умный указатель:

std::unique_prt<MyClass> foo(new MyClass);
// or
auto bar = MyClass::make();
bar->doSomething();

Будет такая же производительность, как в и Qt. Прирост скорости ожидается в случае аллокации объекта в куче, за счёт удаления второй аллокации.

auto foo = MyObject::make("hello");
foo->setParent(bar);
Dendy ★★★★★
() автор топика
Ответ на: комментарий от UVV

Если мы об embedded, то из приведённой тобой статистики имеет смысл только QML Video.

Вообще-то embedded не одним QML живёт, виджеты и софтверный рендеринг там тоже имеют место быть. Но если же зашла речь о QML, то я там же дал пример простой игры в составе приметов Qt — samegame: http://doc.qt.io/qt-5/qtquick-demos-samegame-example.html

Over 5000 аллокаций QObject'ов при старте как бе намекают.

Dendy ★★★★★
() автор топика
Ответ на: комментарий от RazrFalcon

QVector так и устроен.

Заглянул в исходники, похоже таки да. Видимо перепутал с QList или ещё чем-то.

Dendy ★★★★★
() автор топика
Ответ на: комментарий от UVV

Не устраивает malloc - замути кастомный аллокатор, не?

С malloc всё в порядке, проблемы с двойной аллокацией указателей для объектов в куче. Никакой даже самый умный аллокатор не спасёт от фрагментации памяти и двойной адресации.

Dendy ★★★★★
() автор топика
Ответ на: комментарий от Dendy

На стеке объекты конечно же можно выделять, только через умный указатель

Т.е. чтобы избавиться от одной динамической аллокации нужно создать одну динамическую аллокацию в вызывающем коде. Ок.

annulen ★★★★★
()
Ответ на: комментарий от RazrFalcon

Можете показать асм обоих вариантов?

Сейчас попробую сделать честное сравнение.

Вы планируете эту идею показать разрабам Qt или для себя?

Для начала хочу понять не занимаюсь ли я идиотской затеей. А то может тема давно обсосана и всем давно известны причины почему так не делают. А там можно идею и в рассылку закинуть.

Dendy ★★★★★
() автор топика
Последнее исправление: Dendy (всего исправлений: 1)
Ответ на: комментарий от annulen

Т.е. чтобы избавиться от одной динамической аллокации нужно создать одну динамическую аллокацию в вызывающем коде.

Она и так всегда минимум одна. В нашем случае она одна всегда. В случае Qt её две для объектов в куче, то-есть практически для всех объектов, что создаются в рантайме.

Dendy ★★★★★
() автор топика
Ответ на: комментарий от Dendy

практически для всех объектов, что создаются в рантайме.

Спорное утверждение, для полей классов обычно можно обойтись без new

annulen ★★★★★
()
Ответ на: комментарий от annulen

А еще можно убрать pimpl и не останется ни одной

Тогда нарушится бинарная совместимость, мы этот вариант здесь не рассматриваем.

Хотя сама идея мне нравится, есть достаточно проектов, в которых Qt поставляется вместе с приложением или даже линкуется статически. Для них размен бинарной совместимости на скорость работы только плюс. Если бы можно было на этапе сборки Qt убрать PIMPL вообще, то было бы шикарно.

Dendy ★★★★★
() автор топика
Ответ на: комментарий от fluorite

Конечно, ты не первый http://llvm.org/devmtg/2015-04/slides/pimpl.pdf

Супер. Ну хоть это доказывает, что сама идея жизнеспособна и не упирается в технические ограничения языка. Хотя вариант с зависимостью от конкретного компилятора точно не протолкнуть в Qt. Всё-таки то что можно то должно решаться на уровне библиотеки. Или стандарта языка.

Dendy ★★★★★
() автор топика
Ответ на: комментарий от Dendy

Давайте сравним с реализацией Qt. Внутри каждого (!) метода они сначала разименовывают указатель на приватные данные и сохраняют его в локальной переменной.

Тебе точно так же надо будет сохранять результат вызова to_private() в локальной переменной, если она используется несколько раз. В коде Qt такое сплошь и рядом. Поэтому сравнение сводится к this->d_ptr против твоего this->to_private(). Посмотрим на реальные тесты конечно, может со всеми последними примочками и додрочками второй вариант будет не медленнее первого.

archie
()
Ответ на: комментарий от archie

Поэтому сравнение сводится к this->d_ptr против твоего this->to_private()

Я уже переделал, чтобы в to_private() просто прибавлялась константа sizeof(Private) к this, работает. Вон по ссылке в PDF человек не соврёт:

Over 50% speedup in allocation-heavy benchmark

Dendy ★★★★★
() автор топика
Ответ на: комментарий от Dendy

Ок. Но проблема с выделением контейнеров и прочих объектов на стеке все равно остается. Дело даже не в производительности, это крайне неудобно для программиста. Просто представь себе код, где надо будет каждую строку или массив динамически создавать в куче или через std::unique_ptr.

archie
()
Ответ на: комментарий от Dendy

Никакой даже самый умный аллокатор не спасёт от фрагментации памяти и двойной адресации.

Efficiency Considerations for malloc

To make the best use of malloc, it helps to know that the GNU version of malloc always dispenses small amounts of memory in blocks whose sizes are powers of two. It keeps separate pools for each power of two. This holds for sizes up to a page size. Therefore, if you are free to choose the size of a small block in order to make malloc more efficient, make it a power of two.

Once a page is split up for a particular block size, it can't be reused for another size unless all the blocks in it are freed. In many programs, this is unlikely to happen. Thus, you can sometimes make a program use memory more efficiently by using blocks of the same size for many different purposes.

When you ask for memory blocks of a page or larger, malloc uses a different strategy; it rounds the size up to a multiple of a page, and it can coalesce and split blocks as needed.

The reason for the two strategies is that it is important to allocate and free small blocks as fast as possible, but speed is less important for a large block since the program normally spends a fair amount of time using it. Also, large blocks are normally fewer in number. Therefore, for large blocks, it makes sense to use a method which takes more time to minimize the wasted space.

дополнительно смотри https://en.wikipedia.org/wiki/Slab_allocation, увидь, что sizeofs of QWidget and QObject are respectivly 40 and 16 bytes and then разупорись.

в идеале, sizeof pimpl object будер равен размеру указателя + данные маллока. короче продолжай разупариватся борясь с ветряными мельницами. да и вообще, ты кути на атмеге запускаеш там штоле? или на пике? о каком ємбедед идьот речь? у меня на столе бигл блек с сервером на єрланге управляет железом по i2c, spi, rs232, обрабатывает данные и по сети данные отдает, а у тебя пимпл тормозит. пц. нехрен псу занятся, так он яйца лижет.

anonymous
()
Ответ на: комментарий от archie

Про строки или массивы сейчас речи не идёт, только про объекты, которые мы все привыкли выделять динамически и работать по указателям. Я считаю unique_ptr более читабельным вариантом, смотря на тип или сигнатуру метода сразу понятна семантика использования. Смотря на голый указатель ничего не понятно кто ответственен за его удаление.

Сделал маленький тест:
https://github.com/dendy/zeropimpl/blob/master/test_pimpl.h
https://github.com/dendy/zeropimpl/blob/master/test_zeropimpl.h

100 млн аллокаций, gcc 4.8.5, -O3.

PIMPL: 5.27011 секунд
Zero PIMPL: 3.10456 секунд

cast RazrFalcon

Dendy ★★★★★
() автор топика
Ответ на: комментарий от anonymous

Я прекрастно понимаю, что выделение памяти можно оптимизировать. Но зачем оптимизировать то, что можно вообще выкинуть без потери функционала? Никакой маллок не будет работать быстрее его отсутствия. Здесь разговор не про оптимизацию, а про избежание преждевременной пессимизации.

Dendy ★★★★★
() автор топика
Ответ на: комментарий от Dendy

как раз наоборот. ты пытаешся оптимизировать то, что и так прекрасно работает.

оно конечно имеет недостатки выше тобой перечисленные, но смотря из далека и учитывая все факторы, по соотношеню cons/pros оно делает твои перегруженые малоки как тузик грелку.

anonymous
()
Ответ на: комментарий от anonymous

и да, запретить создание на стеке объекта можно если сделать приватный деструктор.

anonymous
()
Ответ на: комментарий от Dendy

Хотя вариант с зависимостью от конкретного компилятора точно не протолкнуть в Qt.

Отнюдь, это наиболее перспективный вариант. К каждому объявлению d-указателя добавляется что-нибудь вроде Q_PIMPL, в qcompilerdetection.h он определяется в пустоту или кастомный атрибут. Но для начала нужно, чтобы это смержили в транк clang

annulen ★★★★★
()
Ответ на: комментарий от Dendy

А вот любой вариант с обязательным выделением памяти через new не пройдет со 100% вероятностью

annulen ★★★★★
()
Последнее исправление: annulen (всего исправлений: 2)

Возьмём к примеру библиотеку Qt, объектная модель которой полностью построена на PIMPL и наследовании от базового QObject.

А это, кстати, не соответствует реальности, в Qt полно классов, не наследующихся от QObject и/или не использующих PIMPL

annulen ★★★★★
()
Ответ на: комментарий от annulen

К каждому объявлению d-указателя добавляется что-нибудь вроде Q_PIMPL

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

А вот любой вариант с обязательным new не пройдет со 100% вероятностью

В моём варианте нет обязательного new. Вот пример: https://github.com/dendy/zeropimpl/blob/master/abc.h#L47

Dendy ★★★★★
() автор топика
Ответ на: комментарий от annulen

в Qt полно классов, не наследующихся от QObject и/или не использующих PIMPL

В пределах этой темы мы рассматриваем только объектную модель на основе QObject.

Dendy ★★★★★
() автор топика
Ответ на: комментарий от Dendy

Задача не решается одними только аттрибутами компилятора

Судя по презентации, решается

«// only need to add one attribute»

Врут?

В моём варианте нет обязательного new

Фишка в том, что для любого класса, использующего pimpl, должна быть возможность создания на стэке, без наследований и других костылей. Иначе это никуда не пойдет.

annulen ★★★★★
()
Ответ на: комментарий от annulen

Врут?

В PDF рассматривается тривиальный случай, в Qt исходники посложней, но больших правок быть не должно в любом случае.

Фишка в том, что для любого класса, использующего pimpl, должна быть возможность создания на стэке

PIMPL вам ничего не должен, это просто один из способов скрыть реализацию, к стеку он отношения не имеет. Абстрактный класс вон тоже на стеке нельзя создать, хотя он призван решать ту же задачу.

Dendy ★★★★★
() автор топика
Ответ на: комментарий от Dendy

PIMPL вам ничего не должен

А я и не утвержаю, что PIMPL мне что-то должен. Я просто констатирую факт, что он должен делать для того, чтобы соответствующий патч взяли в Qt

annulen ★★★★★
()

нипанятна. На стеке не создашь, в вектор не сунешь, в placement new тоже не умеет. А если нужно миллиарды объектов выделять и malloc это притык, то есть более другие методы. Часто тебе приходится в Qt делать миллионы QObject?

anonymous
()
Ответ на: комментарий от anonymous
    auto as = std::make_shared<C>( 10 );
    as->setValue(1000);
    std::cout << as->value( ) << "\n";

и п..ц. в кору сразу. Плохой, негодный метод.

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.