Цепные вызовы для временных пользователей в C++
У меня есть класс, который делает преобразование на строку, например, так
class transer{
transer * parent;
protected:
virtual string inner(const string & s) = 0;
public:
string trans(const string & s) {
if (parent)
return parent->trans(inner(s));
else
return inner(s);
}
transer(transer * p) : parent(p) {}
template <class T>
T create() { return T(this); }
template <class T, class A1> // no variadic templates for me
T create(A1 && a1) { return T(this, std::forward(a1)); }
};
Так что я могу создать подкласс
class add_count : public transer{
int count;
add_count& operator=(const add_count &);
protected:
virtual string inner(const string & s) {
return std::to_string((long long)count++) + s;
}
public:
add_count(transer * p = 0) : transer(p), count(0) {}
};
И тогда я могу использовать преобразования:
void use_transformation(transer & t){
t.trans("string1");
t.trans("string2");
}
void use_transformation(transer && t){
use_trasnformation(t);
}
use_transformation(add_count().create<add_count>());
Есть ли лучший дизайн для этого? Я хотел бы избежать использования динамического выделения / shared_ptr, если смогу, но я не уверен, будут ли временные объекты оставаться живыми на протяжении всего вызова. Я также хочу иметь возможность иметь каждый transer
иметь возможность разговаривать со своим родителем во время уничтожения, поэтому временные также должны быть уничтожены в правильном порядке. Также сложно создать связанное преобразование и сохранить его на потом, так как
sometrans t = add_count().create<trans1>().create<trans2>().create<trans3>();
будет сохранять указатели на временные, которые больше не существуют. Делать что-то вроде
trans1 t1;
trans2 t2(&t1);
trans3 t3(&t2);
было бы безопасно, но раздражает. Есть ли лучший способ для выполнения таких операций?
2 ответа
Временные значения будут уничтожены в конце полного выражения, в обратном порядке их построения. Однако будьте осторожны с последним, поскольку нет никаких гарантий относительно порядка оценки. (За исключением, конечно, прямой зависимости: если вам нужен один временный объект для создания следующего - и если я правильно понял, это ваш случай - тогда вы в безопасности.)
Если вам не нужно динамическое распределение, вы либо передаете данные, которые используются, в функцию, которая инициирует цепочку, либо вам нужен корневой тип, который хранит их для вас (если вы не хотите чрезмерного копирования). Пример (может не скомпилироваться):
struct fooRef;
struct foo
{
fooRef create() { return fooRef( m_Val ); }
foo& operator=( const fooRef& a_Other );
std::string m_Val;
}
struct fooRef
{
fooRef( std::string& a_Val ) : m_Val( a_Val ) {}
fooRef create() { return fooRef( m_Val ); }
std::string& m_Val;
}
foo& foo::operator=( const fooRef& a_Other ) { m_Val = a_Other.m_Val; }
foo startChain()
{
return foo();
}
foo expr = startChain().create().create(); // etc
Сначала строка лежит на временном foo, созданном из startChain(), все связанные операции работают с этими исходными данными. Затем присваивание, наконец, копирует значение в именованную переменную. Вы можете, вероятно, почти гарантировать RVO на startChain().