C++ постоянное ссылочное время жизни (контейнерный адаптер)

У меня есть код, который похож на это:

class T {};

class container {
 const T &first, T &second;
 container(const T&first, const T & second);
};

class adapter : T {};

container(adapter(), adapter());

Я думал, что время жизни постоянной ссылки будет временем жизни контейнера. Однако это появляется иначе, объект адаптера уничтожается после того, как контейнер создается, оставляя повисшую ссылку.

Что корректным является время жизни?

является объемом стека временного объекта адаптера объем контейнерного объекта или контейнерного конструктора?

как правильно реализовать обязательный временный объект для классификации членской ссылки?

Спасибо

15
задан Potatoswatter 2 September 2015 в 01:56
поделиться

5 ответов

Согласно стандарту C++03, временная привязка к ссылке имеет различное время жизни в зависимости от контекста. В вашем примере, я думаю, применима выделенная часть ниже (12.2/5 "Временные объекты"):

Временный объект, с которым связана ссылка, или временный объект, который является полным объектом для подобъекта, с которым связан временный объект, сохраняется в течение времени жизни ссылки, за исключением случаев, указанных ниже. Временное, связанное с членом ссылки в ctor-инициализаторе конструктора (12.6.2), сохраняется до выхода конструктора. Временная привязка к параметру ссылки в вызове функции (5.2.2) сохраняется до завершения полного выражения, содержащего вызов.

Таким образом, хотя привязка временного объекта является продвинутой техникой для увеличения времени жизни временного объекта (GotW #88: A Candidate For the "Most Important const"), в данном случае она, очевидно, не поможет.

С другой стороны, у Эрика Ниблера есть статья, которая может вас заинтересовать, в которой обсуждается интересная (хотя и запутанная) техника, позволяющая конструкторам вашего класса определять, был ли ему передан временный объект (фактически rvalue) (и поэтому его нужно скопировать) или не временный (lvalue) (и поэтому потенциально можно безопасно хранить ссылку вместо копирования):

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

И я должен упомянуть, что ссылки rvalue в C++0x должны сделать технику Ниблера ненужной. Ссылки на значения будут поддерживаться в MSVC 2010, который должен выйти через неделю или около того (12 апреля 2010, если я правильно помню). Я не знаю, как обстоят дела с rvalue references в GCC.

17
ответ дан 1 December 2019 в 03:14
поделиться

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

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

-1
ответ дан 1 December 2019 в 03:14
поделиться

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

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

Вероятно, это более проблематично, если вы хотите вызвать конструктор не по умолчанию содержащегося типа. У C ++ 0x для этого есть лучшие решения.

В качестве упражнения контейнер может принимать T или объект, содержащий аргументы для конструктора T. Это по-прежнему полагается на RVO (оптимизация возвращаемого значения).

template <class T1>
class construct_with_1
{
    T1 _1;
public:
    construct_with_1(const T1& t1): _1(t1) {}
    template <class U>
    U construct() const { return U(_1); }
};

template <class T1, class T2>
class construct_with_2
{
    T1 _1;
    T2 _2;
public:
    construct_with_2(const T1& t1, const T2& t2): _1(t1), _2(t2) {}
    template <class U>
    U construct() const { return U(_1, _2); }
};

//etc for other arities

template <class T1>
construct_with_1<T1> construct_with(const T1& t1)
{
    return construct_with_1<T1>(t1);
}

template <class T1, class T2>
construct_with_2<T1, T2> construct_with(const T1& t1, const T2& t2)
{
    return construct_with_2<T1, T2>(t1, t2);
}

//etc
template <class T>
T construct(const T& source) { return source; }

template <class T, class T1>
T construct(const construct_with_1<T1>& args)
{
    return args.template construct<T>();
}

template <class T, class T1, class T2>
T construct(const construct_with_2<T1, T2>& args)
{
    return args.template construct<T>();
}

template <class T>
class Container
{
public:
    T first, second;

    template <class T1, class T2>
    Container(const T1& a = T1(), const T2& b = T2()) : 
        first(construct<T>(a)), second(construct<T>(b)) {}
}; 

#include <iostream>

class Test
{
    int n;
    double d;
public:
    Test(int a, double b = 0.0): n(a), d(b) { std::cout << "Test(" << a << ", " << b << ")\n"; }
    Test(const Test& x): n(x.n), d(x.d) { std::cout << "Test(const Test&)\n"; }
    void foo() const { std::cout << "Test.foo(" << n << ", " << d << ")\n"; }
};

int main()
{
    Test test(4, 3.14);
    Container<Test> a(construct_with(1), test); //first constructed internally, second copied
    a.first.foo();
    a.second.foo();
}
0
ответ дан 1 December 2019 в 03:14
поделиться

Ссылка будет существовать в течение всего времени существования контейнера , но объект , на который ссылается , будет существовать только для время жизни этого объекта. В этом случае вы связали свою ссылку с временным объектом с автоматическим выделением памяти («выделение стека», если хотите, хотя это не номенклатура C ++).Следовательно, вы не можете ожидать, что временное будет существовать за пределами оператора, в котором оно было написано (поскольку оно выходит из области видимости сразу после вызова конструктора для контейнера ). Лучший способ справиться с этим - использовать копию вместо ссылки. Поскольку вы используете константную ссылку, в любом случае она будет иметь аналогичную семантику.

Вам следует переопределить свой класс как:

template<typename T> 
class container 
{
    public:
        container(const T& first, const T& second) : first(first), second(second) {}
    private:
        const T first;
        const T second;
};

В качестве альтернативы вы можете дать своим объектам имя, чтобы они не выходили за пределы области видимости:

   adaptor first;
   adaptor second;
   container c(first,second);

Однако я не думаю, что это хорошая идея, поскольку такой оператор поскольку return c недопустим.

Править
Если вашей целью является совместное использование объектов, чтобы избежать затрат на копирование, вам следует рассмотреть возможность использования объектов интеллектуального указателя. Например, мы можем переопределить ваш объект с помощью интеллектуальных указателей следующим образом:

template<typename T> 
class container 
{
    public:
        container(const boost::shared_ptr<const T>& first, const boost::shared_ptr<const T>& second) : first(first), second(second) {}
    private:
        boost::shared_ptr<const T> first;
        boost::shared_ptr<const T> second;
};

Затем вы можете использовать:

boost::shared_ptr<const adaptor> first(new adaptor);
boost::shared_ptr<const adaptor> second(new adaptor);
container<adaptor> c(first,second);

Или, если вы хотите иметь изменяемые копии первой и второй локально:

boost::shared_ptr<adaptor> first(new adaptor);
boost::shared_ptr<adaptor> second(new adaptor);
container<adaptor> c(boost::const_pointer_cast<const adaptor>(first),boost::const_pointer_cast<const adaptor>(second));
1
ответ дан 1 December 2019 в 03:14
поделиться

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

Невозможно изменить / переопределить / продлить этот срок службы временных файлов. Если вам нужен более длительный срок службы, используйте реальный объект, а не временный:

adapter a, b; 
container(a, b); // lifetime is the lifetime of a and b

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

6
ответ дан 1 December 2019 в 03:14
поделиться
Другие вопросы по тегам:

Похожие вопросы: