Подтвердить что ты не робот

Постоянный ресурс ссылки С++ (контейнерный адаптер)

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

class T {};

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

class adapter : T {};

container(adapter(), adapter());

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

Каково правильное время жизни?

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

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

Спасибо

4b9b3361

Ответ 1

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

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

Таким образом, привязка временного является расширенной техникой для продления срока службы временного объекта (GotW # 88: Кандидат для "Наиболее важная константа" ), это, по-видимому, вам не поможет в этом случае.

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

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

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

Ответ 2

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

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

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

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

Ответ 3

Ссылка будет существовать для всего времени жизни container, но объект , на который ссылаются, будет существовать только для времени жизни этого объекта. В этом случае вы привязали свою ссылку на временный объект с автоматическим распределением хранилища ( "распределение стека", если хотите, хотя это не номенклатура С++). Поэтому вы не можете ожидать, что временное существо будет существовать за пределами инструкции, в которой она была написана (поскольку она выходит за пределы области сразу после вызова конструктора для 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));

Ответ 4

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

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

Вероятнее всего, это более проблематично, если вы хотите вызвать конструктор нестандартного типа. С++ 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();
}

Ответ 5

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

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