Здравствуйте, l33thaxor, Вы писали:
L>Да, это прикольно. Пока только один недостаток вижу (если считать, что массив в data_factory правельного размера и выровнен): если требуется возвратить data из функции, вызывающей fetch_data, то нужно либо делать копирование либо протаскивать data_factory. Т.е.:
А, так вам нужно "прикольно", что ж вы сразу-то не сказали Имхо, фокус с аргумент по умолчанию действительно прикольный, но реально такой код я бы использовать не стал: невидимый объект, дохнущий по окончанию full expression — отлично спрятанные грабли... Плюс нарушение стандарта на тему изменения temporaries (mutable это конечно ловко, но это же хак). Но если уж сильно хочется использовать этот вариант, то почему просто не передавать data() как аргумент по умолчанию и делать const_cast<data> — стандарт в обоих случаях нарушается одинаково.
data const & fetch_data( string const & request, data const & tmp = data() )
{
data* p = const_cast<data*>(&tmp);
return tmp;
}
Здравствуйте, l33thaxor, Вы писали:
L>Но иногда storage не может найти данные по запросу, и он их создаёт. И в этом случае по ссылке возвратить data уже нельзя. Именно поэтому текущая версия fetch_data возвращает по значению. Вопрос: как так возвратить data, чтобы в в большинстве случаев не делать копирования, а делать только в тех редких случаях, когда приходится?
Здравствуйте, l33thaxor, Вы писали:
L>В этом случае, когда storage создает новый объект, его прийдется выделять на хипе, а хотелось бы обойтись стеком.
Ну вообще-то вернуть хоть ссылку, хоть указатель на объект, созданный на стеке внутри функции, вне ее — это никуда не годится.
Вопрос — что значит "создает новый объект" ? Один раз вызвали, не нашел, создал. Второй раз — то же ? Первый созданный объект к этому времени должен еще жить? или же можно его память использовать для второго ? Иными словами, их много может быть или в любой момент только один ? Если один — можно банально глобальную переменную завести.
Вот такое дело. Допустим у класса storage есть функция fetch_data, которая возвращает объекты типа data по запросу:
data storage::fetch_data(string const & request);
Пока что data возвращается по значению. Но в большинстве случаев оказывается, что можно было бы возвратить и ссылку на data, сохраненный в storage:
data const & storage::fetch_data(string const & request);
И это было бы гораздо эффективнее текущей версии, так как не надо делать копию data.
Но иногда storage не может найти данные по запросу, и он их создаёт. И в этом случае по ссылке возвратить data уже нельзя. Именно поэтому текущая версия fetch_data возвращает по значению. Вопрос: как так возвратить data, чтобы в в большинстве случаев не делать копирования, а делать только в тех редких случаях, когда приходится?
Здравствуйте, l33thaxor, Вы писали:
L>Вот такое дело. Допустим у класса storage есть функция fetch_data, которая возвращает объекты типа data по запросу: L>
L>Пока что data возвращается по значению. Но в большинстве случаев оказывается, что можно было бы возвратить и ссылку на data, сохраненный в storage: L>
L>И это было бы гораздо эффективнее текущей версии, так как не надо делать копию data.
L>Но иногда storage не может найти данные по запросу, и он их создаёт. И в этом случае по ссылке возвратить data уже нельзя. Именно поэтому текущая версия fetch_data возвращает по значению. Вопрос: как так возвратить data, чтобы в в большинстве случаев не делать копирования, а делать только в тех редких случаях, когда приходится?
shared_ptr подойдет?
Переубедить Вас, к сожалению, мне не удастся, поэтому сразу перейду к оскорблениям.
Здравствуйте, l33thaxor, Вы писали:
L>Здравствуйте, Ops, Вы писали:
Ops>>shared_ptr подойдет?
L>Каким образом? В storage объекты хранятся не ввиде shared_ptr.
В этом случае, когда storage создает новый объект, его прийдется выделять на хипе, а хотелось бы обойтись стеком. Да и shared_ptr со своим deleter'ом выглядить как то тяжеловато тоже. Больше никаких нет вариантов?
Здравствуйте, l33thaxor, Вы писали:
L>Здравствуйте, Ops, Вы писали:
Ops>>Здравствуйте, l33thaxor, Вы писали:
L>>>Здравствуйте, Ops, Вы писали:
Ops>>>>shared_ptr подойдет?
L>>>Каким образом? В storage объекты хранятся не ввиде shared_ptr.
Ops>>http://www.boost.org/doc/libs/1_45_0/libs/smart_ptr/sp_techniques.html#static
L>В этом случае, когда storage создает новый объект, его прийдется выделять на хипе, а хотелось бы обойтись стеком. Да и shared_ptr со своим deleter'ом выглядить как то тяжеловато тоже. Больше никаких нет вариантов?
Ну так все зависит от того, насколько тяжелые объекты и как потом с ними работают. А вместо выделения в хипе можно использовать пул для этих объектов, или даже просто placement new — тут все от многопоточности зависит.
Переубедить Вас, к сожалению, мне не удастся, поэтому сразу перейду к оскорблениям.
Здравствуйте, ariets, Вы писали:
A>Здравствуйте, l33thaxor, Вы писали:
A>Вопрос: как так возвратить data, чтобы в в большинстве случаев не делать копирования, а делать только в тех редких случаях, когда приходится?
A>
...
A> staticconst data emptyData;
A> return emtyData;
A>}
A>
у вас же переменная static, возвращайте всегда ссылку.
A>const data &fetch_data( const std::string &request )
A>{
A> if( is_data( request ) )
A> {
A> return get_data( request );
A> }
A> static const data emptyData;
A> return emtyData;
A>}
A>
Статический объект не подходит. В этом случае не будет работать даже простейшая последовательность двух вызовов fetch_data:
data const & d1 = fetch_data(r1);
data const & d2 = fetch_data(r2); // d1 is clobbered
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>Вопрос — что значит "создает новый объект" ? Один раз вызвали, не нашел, создал. Второй раз — то же ? Первый созданный объект к этому времени должен еще жить? или же можно его память использовать для второго ?
Здравствуйте, _nn_, Вы писали:
__>Здравствуйте, l33thaxor, Вы писали:
__>Имеется возможность произвести move ? __>Возможно это может будет эффективней всего.
__>С++0х ждать для move не обязательно, можно его реализовать вручную как в std::auto_ptr делается.
Непонятно, как мне это поможет. Мне нужно оптимизировать для случая возврата существующего объекта по ссылке. Это fast path. Возврат по значению, когда происходит создание нового объекта и копирование его при возврате из функции, это slow path. Она происходит редко и его оптимизировать не надо.
Здравствуйте, l33thaxor, Вы писали:
L>Здравствуйте, Pavel Dvorkin, Вы писали:
PD>>Вопрос — что значит "создает новый объект" ? Один раз вызвали, не нашел, создал. Второй раз — то же ? Первый созданный объект к этому времени должен еще жить? или же можно его память использовать для второго ?
L>Должен жить. Память переиспользовать нельзя.
Тогда я не понимаю, как ты хочешь вернуть данные, расположенные на стеке. Допустим на минуту, что это возможно. Сделать-то как ? Нельзя же описать неопределенное число автоматических переменных.
Здравствуйте, Pavel Dvorkin, Вы писали:
L>>Должен жить. Память переиспользовать нельзя.
PD>Тогда я не понимаю, как ты хочешь вернуть данные, расположенные на стеке. Допустим на минуту, что это возможно. Сделать-то как ? Нельзя же описать неопределенное число автоматических переменных.
Я не хочу вернуть данные на стеке. Вопрос заключался в следующем: если данные уже существуют в storage, то вернуть их так же быстро как по сслыке, если же данных нет, то создать из и вернуть по значению.
На самом деле вот один вариант интерфейса, который удовлетворяет данному требованию. Я не хотел его сразу приводить, думал, что кто-нибудь приведет что-нибудь лучше.
// temp_obj - [in/out] указатель на временный объект, переданный из вызывающей стороны,
// в который будет положен объект data, если нельзя вернуть ссылку и необходимо создать новый объект
data const * storage::fetch_data(data * temp_obj, string const & request) {
if (data const * existing = retrieve_data(request)) {
return existing;
}
create_data(request).swap(*temp_obj);
return temp_obj;
}
// вызов
data temp_obj;
data const * fetched = storage.fetch_data(&temp_obj, request);
// работаем с fetched
// при выходе из scope, temp_obj освободит объект, который мог создать fetch_data
Здесь удовлетворены требования по производительности. Но мне этот интерфейс не нравится. Во-первых, вызов fetch_data немного кривой получается. А во-вторых, получается, что fetch_data имеет некоторый неоднозначный side effect.
Можно ли реализовать подобную идею с более приличным интерфейсом.
Здравствуйте, l33thaxor, Вы писали:
L>Здравствуйте, Pavel Dvorkin, Вы писали:
L>>>Должен жить. Память переиспользовать нельзя.
PD>>Тогда я не понимаю, как ты хочешь вернуть данные, расположенные на стеке. Допустим на минуту, что это возможно. Сделать-то как ? Нельзя же описать неопределенное число автоматических переменных.
L>Я не хочу вернуть данные на стеке. Вопрос заключался в следующем: если данные уже существуют в storage, то вернуть их так же быстро как по сслыке, если же данных нет, то создать из и вернуть по значению.
L>На самом деле вот один вариант интерфейса, который удовлетворяет данному требованию. Я не хотел его сразу приводить, думал, что кто-нибудь приведет что-нибудь лучше.
<skipped>
L>Здесь удовлетворены требования по производительности. Но мне этот интерфейс не нравится. Во-первых, вызов fetch_data немного кривой получается. А во-вторых, получается, что fetch_data имеет некоторый неоднозначный side effect.
L>Можно ли реализовать подобную идею с более приличным интерфейсом.
Честно говоря, мне все это совсем не нравится. Создается какой-то непонятный (и, возможно, ненужный) объект, потом внутри в него пересылают данные из вновь созданного, и все это ради того, чтобы этот объект потом мог сам удалиться на выходе из scope.
Я бы просто сделал. Вернул бы указатель то ли на найденный, то ли на прямо там созданный объект и завел бы еще один параметр, указывающий был ли объект создан или нет. Если нет — delete его и все дела.
data const * storage::fetch_data(string const & request, bool& wasFound) {
if (data const * existing = retrieve_data(request)) {
wasFound = true;
return existing;
}
wasFound = false
return create_data(request)
}
...
bool wasFound;
data const * result = fetch_data(request,wasFound);
// use resultif(!wasFound)
delete result;
А можно чуть похитрее
class data_and_bool
{
data* result;
bool wasFound;
~data_and_bool()
{
if(!wasFound)
delete data;
}
};
data_and_bool storage::fetch_data(string const & request) {
data_and_bool dab;
if (data const * existing = retrieve_data(request)) {
dab.wasFound = true;
dab.result = existing;
}
else {
dab.wasFound = false;
dab.result = create_data(request);
}
return dab;
}
data_and_bool dab = storage::fetch_data(request);
// и пусть деструктор уничтожает данные на выходе из scope, если они были созданы
}
Естественно, не компилировал, так что за возможные ошибки сорри. Считай это псевдокодом.
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>Я бы просто сделал. Вернул бы указатель то ли на найденный, то ли на прямо там созданный объект и завел бы еще один параметр, указывающий был ли объект создан или нет. Если нет — delete его и все дела.
Такая идея мне в голову приходила, но хотелось бы обойтись без создания объектов на хипе, если можно ограничиться стеком. В том интерфейсе, который я привел (с временным temp_obj), объекты создаются на стеке.
Если бы хип был приемлимым, то я бы выбрал вариант с shared_ptr, который был упомянут в первых ответах.
Здравствуйте, l33thaxor, Вы писали:
L>Такая идея мне в голову приходила, но хотелось бы обойтись без создания объектов на хипе, если можно ограничиться стеком. В том интерфейсе, который я привел (с временным temp_obj), объекты создаются на стеке.
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>Здравствуйте, l33thaxor, Вы писали:
L>>Такая идея мне в голову приходила, но хотелось бы обойтись без создания объектов на хипе, если можно ограничиться стеком. В том интерфейсе, который я привел (с временным temp_obj), объекты создаются на стеке.
PD>Хм, а create_data где его создает ?
А ещё можно p_existing вместо 0 заменить на &copied, наверное проще будет. Ну и интерфейс подточить по вкусу
Re: хитрый возврат объекта из функции
От:
Аноним
Дата:
26.01.11 22:41
Оценка:
Здравствуйте, l33thaxor, Вы писали:
L>Вот такое дело. Допустим у класса storage есть функция fetch_data, которая возвращает объекты типа data по запросу: L>
L>Пока что data возвращается по значению. Но в большинстве случаев оказывается, что можно было бы возвратить и ссылку на data, сохраненный в storage: L>
L>И это было бы гораздо эффективнее текущей версии, так как не надо делать копию data.
L>Но иногда storage не может найти данные по запросу, и он их создаёт. И в этом случае по ссылке возвратить data уже нельзя. Именно поэтому текущая версия fetch_data возвращает по значению. Вопрос: как так возвратить data, чтобы в в большинстве случаев не делать копирования, а делать только в тех редких случаях, когда приходится?
Если ты в объекте типа data который тебе возвращается не будешь ничего менять и вызывать только константные методы, то
почему бы не
const data& ref = storage::fetch_data(string const & request);
ну а если уж бандитизмом заниматся во благо производительности то
const data& ref = storage::fetch_data(string const & request);
data& ref2 = const_cast<data&>(ref);
Хотя приведенный тобой вариант с передачей указателя на объект типа data в функцию, по моему мнение,вполне себе ничего.
Только я бы ссылку передавал )
Да, это прикольно. Пока только один недостаток вижу (если считать, что массив в data_factory правельного размера и выровнен): если требуется возвратить data из функции, вызывающей fetch_data, то нужно либо делать копирование либо протаскивать data_factory. Т.е.:
data const & proxy_fetch(string const & request) {
return fetch_data(request); // бум!
}
data proxy_fetch(string const & request) {
return fetch_data(request); // OK
}
data const & proxy_fetch(string const & request, data_factory<data> const & tmp = data_factory<data>())
{
return fetch_data(request, tmp); // OK
}
L>Да, это прикольно. Пока только один недостаток вижу (если считать, что массив в data_factory правельного размера и выровнен): если требуется возвратить data из функции, вызывающей fetch_data, то нужно либо делать копирование либо протаскивать data_factory. Т.е.:
data const & ref = fetch_data(request);
// use ref
не пройдет.
чтобы работало надо в любом случае или передавать объект/буфер в функцию, или возвращать по значению, или отказаться от стека.
от этого не уйти.
Здравствуйте, l33thaxor, Вы писали:
L>Да, это прикольно. Пока только один недостаток вижу (если считать, что массив в data_factory правельного размера и выровнен): если требуется возвратить data из функции, вызывающей fetch_data, то нужно либо делать копирование либо протаскивать data_factory. Т.е.:
забыл еще. в деструкторе фабрики надо деструктор вызывать, есть создание прошло.
Здравствуйте, l33thaxor, Вы писали: L>Статический объект не подходит. В этом случае не будет работать даже простейшая последовательность двух вызовов fetch_data: L>
Заведите статический массив на 10 или 100 элементов и используйте циклически. Для произвольного времени жизни результата не подойдёт, но в этом случае не подойдёт и любой из предложенных методов.
Здравствуйте, l33thaxor, Вы писали:
L>Но иногда storage не может найти данные по запросу, и он их создаёт. И в этом случае по ссылке возвратить data уже нельзя. Именно поэтому текущая версия fetch_data возвращает по значению. Вопрос: как так возвратить data, чтобы в в большинстве случаев не делать копирования, а делать только в тех редких случаях, когда приходится?
Можно попробовать создать variant класс, который будет содержать либо ссылку (указатель) на объект либо копию объекта data.
template <typename T>
class ptr_value : boost::variant<T, const T *>
{
private:
typedef boost::variant<T, const T *> base_t;
struct value_visitor : boost::static_visitor<const T *>
{
const T * operator()(const T & val) const
{
return &val;
}
const T * operator()(const T * ptr) const
{
return ptr;
}
};
const T * get_ptr() const
{
return boost::apply_visitor(value_visitor(), static_cast<const base_t &>(*this));
}
public:
explicit
ptr_value(const T & val)
: base_t(val)
{}
explicit
ptr_value(const T * ptr)
: base_t(ptr)
{}
const T * operator->() const
{
return get_ptr();
}
const T & operator*() const
{
return *get_ptr();
}
};
Здравствуйте, l33thaxor, Вы писали:
L>Вот такое дело. Допустим у класса storage есть функция fetch_data, которая возвращает объекты типа data по запросу: L>
L>Пока что data возвращается по значению. Но в большинстве случаев оказывается, что можно было бы возвратить и ссылку на data, сохраненный в storage: L>
L>И это было бы гораздо эффективнее текущей версии, так как не надо делать копию data.
L>Но иногда storage не может найти данные по запросу, и он их создаёт. И в этом случае по ссылке возвратить data уже нельзя. Именно поэтому текущая версия fetch_data возвращает по значению. Вопрос: как так возвратить data, чтобы в в большинстве случаев не делать копирования, а делать только в тех редких случаях, когда приходится?
Как такой вариант?
Здравствуйте, l33thaxor, Вы писали:
L>Но иногда storage не может найти данные по запросу, и он их создаёт. И в этом случае по ссылке возвратить data уже нельзя. Именно поэтому текущая версия fetch_data возвращает по значению. Вопрос: как так возвратить data, чтобы в в большинстве случаев не делать копирования, а делать только в тех редких случаях, когда приходится?
уверены, что RVO/NRVO не пройдет?
да и насколько тяжелый data? если там просто данные байт на 64 быстрее будет работать копирование на стеке, чем одно выделение на куче.
L>Да, это прикольно. Пока только один недостаток вижу (если считать, что массив в data_factory правельного размера и выровнен): если требуется возвратить data из функции, вызывающей fetch_data, то нужно либо делать копирование либо протаскивать data_factory. Т.е.:
Можно data_factory объявить высоко на стеке. сразу после создания потока.