Есть набор фубаров, унаследованных от общего абстрактного предка
class TFubar {};
class TFubarA: public TFubar {};
class TFubarB: public TFubar {};
Есть набор функций, помогающих получить нужный фубар. Причем некоторые их этих функций возвращают константную ссылку на уже существующий фубар, а некоторые возвращают новый фубар по значению.
В общем, что-то наподобие std::pair и std::make_pair
Но сделать это нужно так, чтобы, если в функцию Hold() передать значение фубара, то обертка должна хранить значение фубара (копию).
А если в функцию Hold передать константную ссылку на фубар, то и обертка должна хранить константную ссылку на фубар.
Например:
Hold(GetFubarA()); // должно вернуть TFubarHolder<TFubarA>
Hold(GetFubarRefB()); // должно вернуть TFubarHolder<const TFubarB&> или TFubarHolder<const TFubar&>
Я пытался перегрузить функцию Hold(), чтобы одна вызывалась для ссылок, а другая — для значений. Не выходит — в обоих случаях перегрузка разрешается в пользу какой-то одной функции, а не разных. Либо перегрузка вообще не разрешается.
Есть ли какие-то решения? Есть ли решение, пригодное для MSVC6 (ограниченная поддержка шаблонов, в частности, отсутствие частичной специализации)?
Ниже много лирики, основная мысль в конце выделена жирным.
CEG>Я пытался перегрузить функцию Hold(), чтобы одна вызывалась для ссылок, а другая — для значений. Не выходит — в обоих случаях перегрузка разрешается в пользу какой-то одной функции, а не разных. Либо перегрузка вообще не разрешается.
Различать значения и ссылки очень, очень мучительно.
(Говорят, в C++0x есть rvalue references, но я в этом скорее осло, чем копенгаген).
Если ты имеешь дело с нульарными функциями, то можешь передавать не значение, а собственно функцию.
Выдрать тип из сигнатуры (T() или T const&()) — труда не составит.
Но это уж как-то совсем ограниченное решение.
CEG>Есть ли какие-то решения? Есть ли решение, пригодное для MSVC6 (ограниченная поддержка шаблонов, в частности, отсутствие частичной специализации)?
Мне кажется, правильно будет
1) Сперва задуматься — чем для тебя (в контексте твоей задачи) различаются ссылки и значения.
Ну, скажем, такое различие: значение, будучи временным объектом, живёт недолго; зато его можно копировать.
А так ли страшно, что значение живёт недолго?
Если сам Hold живёт в пределах полного выражения, то ссылка на временный объект вполне всех устроит.
А так ли страшно, что глобальную ссылку нельзя копировать?
Вообще, — страшно: полиморфизм, всё-таки. Но если функции, возвращающие глобальные ссылки, не апкастят тип, эта проблема снимается.
И ещё раз страшно, если эксплуатируется уникальность адреса либо копирование слишком дорого.
2) Постараться каким-то способом придти к единообразию.
Единообразие в виде константных ссылок — ограничено, в силу проблемы времени жизни. (Кстати, из-за этого я проклял библиотеку Локи).
Единообразие в виде значений — это либо беспощадное копирование, либо использование прокси-объектов.
Наконец, можно достичь единообразия в виде умных указателей (тот же boost::shared_ptr может и с глобальными неубиваемыми объектами иметь дело). Куча плюсов и минус в виде нагрузки на динамическую память.
Прокси могут быть
— настоящими, в иерархии твоих TFubar'ов
— резко отличными по типу: начиная с банальных указателей вместо ссылок, и кончая boost::cref и его аналогами
Выбор техники зависит от того, насколько мучительно будет переделать программу.
Грубо говоря, если const Fubar& foo() заменить на cref<Fubar> foo() или на Fubar* foo() — много ли клиентского кода отвалится?
Перекуём баги на фичи!
Re[2]: Как отделить константную ссылку от значения?
К>Различать значения и ссылки очень, очень мучительно. К>(Говорят, в C++0x есть rvalue references, но я в этом скорее осло, чем копенгаген).
както в таком вот акцепте...
#include <iostream>
#include <string>
#include <typeinfo>
#include <cassert>
#include <malloc.h>
#include <cxxabi.h>
#include <boost/type_traits/is_reference.hpp>
using namespace std;
template <typename T>
std::string type_name(const T&)
{
const char* const name = typeid(T).name();
// Try to demangle name using gcc API (of couse if current compiler is gcc)int status;
char* demangled_name = abi::__cxa_demangle(name, 0, 0, &status);
if (!status)
{
std::string name = demangled_name;
free(demangled_name);
return name;
}
assert(!"How is that possible? Fail on demangle name produced by itself??");
return name;
}
class TFubar {};
class TFubarA: public TFubar {};
class TFubarB: public TFubar {};
TFubarA GetFubarA() { return TFubarA(); }
const TFubar& GetFubarRefB() { static TFubarB f; return f; }
template<typename FOOBAR>
class TFubarHolder
{
FOOBAR m_f;
public:
explicit TFubarHolder(FOOBAR f): m_f(f) {}
};
template <typename FOOBAR>
class TFubarHolder<const FOOBAR&>
{
const FOOBAR& m_f;
public:
explicit TFubarHolder(const FOOBAR& f): m_f(f) {}
};
template <typename FOOBAR>
TFubarHolder<FOOBAR> Hold(FOOBAR&& f)
{
return TFubarHolder<FOOBAR>(f);
}
int main()
{
// должно вернуть TFubarHolder<TFubarA>auto f1 = Hold(GetFubarA());
cout << type_name(f1) << endl;
// должно вернуть TFubarHolder<const TFubarB&> или TFubarHolder<const TFubar&>auto f2 = Hold(GetFubarRefB());
cout << type_name(f2) << endl;
return 0;
}
zaufi /work/tests $ g++ -std=gnu++0x -o vr vr.cc
zaufi /work/tests $ ./vr
TFubarHolder<TFubarA>
TFubarHolder<TFubar const&>
zaufi /work/tests $ g++ --version
g++ (Gentoo 4.4.2 p1.0) 4.4.2
Copyright (C) 2009 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Re[2]: Как отделить константную ссылку от значения?
Здравствуйте, Кодт. Большое спасибо за столь развернутый ответ.
Вы писали: К>Если ты имеешь дело с нульарными функциями, то можешь передавать не значение, а собственно функцию.
Идея мне понравилась. возьму на вооружение. Но в данном случае она не годится: функции-хелперы могут иметь произвольное количество аргументов. Кроме того, они могут быть как свободными функциями, так и членами классов.
К>Мне кажется, правильно будет К>1) Сперва задуматься — чем для тебя (в контексте твоей задачи) различаются ссылки и значения. К>Ну, скажем, такое различие: значение, будучи временным объектом, живёт недолго; зато его можно копировать. К>А так ли страшно, что значение живёт недолго? Если сам Hold живёт в пределах полного выражения, то ссылка на временный объект вполне всех устроит.
Функцию Hold() предполагается использовать для создания временного Holder-а для передачи его в некую функцию. Примерно так:
RecognizeFubar(Hold(GetFubar()));
Что будет, если GetFubar() возвращает фубар по значению, а Hold() завернет его в TFubarHolder<const TFubar&>, как константную ссылку? Доживет ли этот временный фубар до возврата из функции RecognizeFubar(). Что-то мне подсказывает, что он имеет право сдохнуть сразу после выхода из конструктора TFubarHolder(), а в функцию RecognizeFubar() FubarHolder придет уже с невалидной ссылкой. Мне кажется, в этом случае надежнее заворачивать в TFubarHolder именно значение, а не ссылку. Хотя, может, я зря опасаюсь и применение здесь ссылок совершенно безопасно?
К>А так ли страшно, что глобальную ссылку нельзя копировать? К> Вообще, — страшно: полиморфизм, всё-таки. Но если функции, возвращающие глобальные ссылки, не апкастят тип, эта проблема снимается.
Ссылки чаще всего будут ссылками на базовый класс TFubar, так что копировать их по значению нельзя. К счастью, TFubar является абстрактным классом, так что компилятор и не позволит их копировать, тем сасым не допустит срезки
К>2) Постараться каким-то способом придти к единообразию. К>Выбор техники зависит от того, насколько мучительно будет переделать программу. К>Грубо говоря, если const Fubar& foo() заменить на cref<Fubar> foo() или на Fubar* foo()- много ли клиентского кода отвалится?
Нет не много. Код пока живет только у меня и в небольшом количестве.
Я правильно понял идею, с прокси, предлагается сделать что-то вроде такого? :
template<typename FOOBAR>
TFubarHolder<FOOBAR> Hold(FOOBAR &f)
{
return TFubarHolder<FOOBAR>(f); // возвращаем по значению
}
template<typename FOOBAR>
TFubarHolder<const FOOBAR&> Hold(reference_wrapper<const FOOBAR> &f)
{
return TFubarHolder<const FOOBAR&>(f.get()); // возвращаем по ссылке
}
TFubarA GetFubarVal();
const TFubar& GetFubarRef();
reference_wrapper<const TFubar> GetFubarCRef();
void DevelopFubar(const TFubar &fubar)
{
RecognizeFubar(Hold(fubar)); // a)
RecognizeFubar(Hold(cref(fubar))); // b) Безопасно, но нужно не забыть cref
RecognizeFubar(Hold(GetFubarRef())); // c)
RecognizeFubar(Hold(GetFubarCRef())); // d) Безопасно, передается ссылка
RecognizeFubar(Hold(GetFubarVal())); // e) Безопасно, скопируется по значению
}
Вызывают опасения случаи a) и с). Если я забуду поставить cref, то может произойти копирование по значению и может возникнуть срезка. Как бы сделать, чтобы при отсутствие cref гарантированно возникала ошибка компиляции?
И вернусь, к вопросу, заданному в начале этого поста.
Может, все-таки, сделать Hold() таким, чтобы он всегда заворачивал ссылку? Будет ли тогда безопасет случай e) ?
Re[3]: Как отделить константную ссылку от значения?
Здравствуйте, ChainEG, Вы писали:
CEG>Функцию Hold() предполагается использовать для создания временного Holder-а для передачи его в некую функцию. Примерно так: CEG>
CEG>RecognizeFubar(Hold(GetFubar()));
CEG>
CEG>Что будет, если GetFubar() возвращает фубар по значению, а Hold() завернет его в TFubarHolder<const TFubar&>, как константную ссылку? Доживет ли этот временный фубар до возврата из функции RecognizeFubar(). Что-то мне подсказывает, что он имеет право сдохнуть сразу после выхода из конструктора TFubarHolder(), а в функцию RecognizeFubar() FubarHolder придет уже с невалидной ссылкой. Мне кажется, в этом случае надежнее заворачивать в TFubarHolder именно значение, а не ссылку. Хотя, может, я зря опасаюсь и применение здесь ссылок совершенно безопасно?
Все временные объекты доживают до конца полного выражения. Так что здесь ссылки безопасны.
......
CEG>Я правильно понял идею, с прокси, предлагается сделать что-то вроде такого? :
...
Идея в том, чтобы Hold всегда держал копию значения. Но вот тип значения может быть как непосредственно TFubarxxx, так и reference_wrapper.