C ++ постоянное время жизни ссылки (контейнерный адаптер) - PullRequest
15 голосов
/ 09 апреля 2010

У меня есть код, который выглядит так:

class T {};

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

class adapter : T {};

container(adapter(), adapter());

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

Каков правильный срок службы?

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

как правильно реализовать привязку временного объекта к ссылке на член класса?

Спасибо

Ответы [ 5 ]

17 голосов
/ 09 апреля 2010

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

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

Таким образом, хотя привязка временного является продвинутой техникой, позволяющей продлить срок службы временного объекта ( ПОЛУЧИЛА № 88: Кандидат на получение "Наиболее важного константа" ), это, очевидно, не поможет вам в этом. это дело.

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

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

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

6 голосов
/ 09 апреля 2010

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

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

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

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

1 голос
/ 09 апреля 2010

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

Вы должны переопределить свой класс как:

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));
0 голосов
/ 09 апреля 2010

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

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

Вероятно, это более проблематично, если вы хотите вызвать конструктор не по умолчанию для содержимого типа. 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 голосов
/ 09 апреля 2010

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

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

...