Jak to możliwe, że odniesienie nie-const nie może powiązać z obiektem tymczasowym?

Dlaczego nie można uzyskać odniesienia non-const do obiektu tymczasowego, która Funkcja getx() zwraca? Oczywiście jest to zabronione przez Standard C++ ale interesuje mnie cel takiego ograniczenia, , a nie odniesienie do standardu.

struct X
{
    X& ref() { return *this; }
};

X getx() { return X();}

void g(X & x) {}    

int f()
{
    const X& x = getx(); // OK
    X& x = getx(); // error
    X& x = getx().ref(); // OK
    g(getx()); //error
    g(getx().ref()); //OK
    return 0;
}
  1. jest jasne, że żywotność obiektu nie może być przyczyną, ponieważ stałe odniesienie do obiektu jest nie zabronione przez Standard C++.
  2. jest jasne, że obiekt tymczasowy nie jest stała w powyższej próbce, ponieważ dozwolone są wywołania funkcji niestałych. Na przykład, {[3] } może zmodyfikować obiekt tymczasowy.
  3. dodatkowo, ref() pozwala oszukać kompilator i uzyskać link do tego tymczasowego obiektu, co rozwiązuje nasz problem.

Dodatkowo:

Mówią: "przypisanie tymczasowego obiektu do odniesienia const wydłuża żywotność tego obiektu" i "nic nie jest powiedziane o odniesieniach nie-const". My dodatkowe pytanie . Czy następujące przypisanie wydłuża żywotność obiektu tymczasowego?

X& x = getx().ref(); // OK
Author: curiousguy, 2009-10-14

11 answers

Z tego Visual C++ blog artykułu o rvalue reference :

... C++ nie chce przypadkiem modyfikować, ale bezpośrednio wywołanie funkcji non-const member na modyfikowalna wartość R jest jawna, więc to dozwolone ...

Zasadniczo, nie powinieneś próbować modyfikować tymczasowych obiektów z tego powodu, że są one tymczasowymi obiektami i umrą w każdej chwili. Powodem, dla którego możesz wywoływać metody non-const jest to, że, cóż, jesteś zapraszamy do robienia "głupich" rzeczy, o ile wiesz, co robisz i jesteś o tym szczery (np. używając reinterpret_cast). Ale jeśli połączysz tymczasowe odniesienie z odniesieniem non-const, możesz je przekazywać "na zawsze", aby Twoja manipulacja obiektem zniknęła, ponieważ gdzieś po drodze całkowicie zapomniałeś, że to było tymczasowe.

Gdybym był tobą, przemyślałbym projekt moich funkcji. Dlaczego g () przyjmuje referencję, czy modyfikuje parametr? Jeśli nie, zrób to const reference, jeśli tak, dlaczego próbujesz przekazać tymczasowe do niego, nie obchodzi cię, że to tymczasowe modyfikujesz? Dlaczego getx () zwraca tymczasowo? Jeśli podzielisz się z nami swoim prawdziwym scenariuszem i tym, co próbujesz osiągnąć, możesz uzyskać dobre sugestie, jak to zrobić.

Przeciwstawianie się językowi i oszukiwanie kompilatora rzadko rozwiązuje problemy - zazwyczaj stwarza problemy.


Edit: odpowiadanie na pytania w komentarzu: 1) X& x = getx().ref(); // OK when will x die? - Nie wiem wiem i nie obchodzi mnie to, bo to jest dokładnie to, co mam na myśli mówiąc "wbrew językowi". Język mówi: "czasowniki umierają na końcu wypowiedzi, chyba że są związane z const reference, w którym to przypadku umierają, gdy odniesienie wychodzi poza zakres". Stosując tę regułę, wydaje się, że x jest już martwy na początku następnego polecenia, ponieważ nie jest związany z referencją const (kompilator nie wie, co zwraca ref ()). Jest to jednak tylko przypuszczenie.

2) określiłem cel ewidentnie: nie wolno modyfikować tymczasowych, bo to po prostu nie ma sensu (ignorowanie referencji C++0x rvalue). Pytanie " to dlaczego wolno mi dzwonić do członków spoza const?"jest dobry, ale nie mam lepszej odpowiedzi niż ta, którą już powiedziałem powyżej.

3) cóż, jeśli mam rację co do X w X& x = getx().ref(); umierania na końcu wypowiedzi, problemy są oczywiste.

W każdym razie, bazując na twoim pytaniu i komentarzach, nie sądzę, aby nawet te dodatkowe odpowiedzi Cię zadowoliły. Oto ostateczna próba / podsumowanie: Komitet C++ zdecydował, że nie ma sensu modyfikować tymczasowych, dlatego nie zezwolił na wiązanie się z referencjami non-const. Może być jakaś implementacja kompilatora lub problemy historyczne były również zaangażowane, Nie wiem. Potem pojawił się jakiś szczególny przypadek i zdecydowano, że wbrew wszelkim przeciwnościom nadal będą one umożliwiać bezpośrednią modyfikację poprzez wywołanie metody non-const. Ale to jest wyjątek - na ogół nie wolno modyfikować tymczasowych. Tak, C++ jest często tak dziwnie.
 84
Author: sbk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-14 14:42:30

W kodzie getx() zwraca obiekt tymczasowy, tzw. "rvalue". Można kopiować wartości R do obiektów (aka. zmienne) lub powiązać je z referencjami const (co wydłuży ich żywotność do końca życia referencji). Nie można powiązać wartości R z referencjami non-const.

Była to celowa decyzja projektowa, aby uniemożliwić użytkownikom przypadkową modyfikację obiektu, który umrze na końcu wyrażenia:

g(getx()); // g() would modify an object without anyone being able to observe

Jeśli chcesz to zrobić, możesz będzie musiał najpierw utworzyć lokalną kopię lub obiekt lub powiązać go z referencją const:

X x1 = getx();
const X& x2 = getx(); // extend lifetime of temporary to lifetime of const reference

g(x1); // fine
g(x2); // can't bind a const reference to a non-const reference

Zauważ, że następny standard C++ będzie zawierał referencje rvalue. To, co znasz jako referencje, staje się zatem nazywane "referencjami lvalue". Będziesz mógł powiązać rvalues z referencjami rvalue i możesz przeciążać funkcje na "rvalue-ness":

void g(X&);   // #1, takes an ordinary (lvalue) reference
void g(X&&);  // #2, takes an rvalue reference

X x; 
g(x);      // calls #1
g(getx()); // calls #2
g(X());    // calls #2, too

Idea referencji rvalue polega na tym, że skoro te obiekty i tak umrą, możesz skorzystać z z tej wiedzy i wdrożyć to, co nazywa się "semantyką ruchu", pewien rodzaj optymalizacji:

class X {
  X(X&& rhs)
    : pimpl( rhs.pimpl ) // steal rhs' data...
  {
    rhs.pimpl = NULL; // ...and leave it empty, but deconstructible
  }

  data* pimpl; // you would use a smart ptr, of course
};


X x(getx()); // x will steal the rvalue's data, leaving the temporary object empty
 30
Author: sbi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-20 17:24:47

Pokazujesz, że łączenie operatorów jest dozwolone.

 X& x = getx().ref(); // OK

Wyrażenie to ' getx ().ref (); "i jest to wykonywane do końca przed przypisaniem do "x".

Zauważ, że getx() nie zwraca referencji, ale w pełni uformowanego obiektu do lokalnego kontekstu. Obiekt jest tymczasowy, ale to Nie const, co pozwala wywoływać inne metody w celu obliczenia wartości lub wywoływać inne skutki uboczne.

// It would allow things like this.
getPipeline().procInstr(1).procInstr(2).procInstr(3);

// or more commonly
std::cout << getManiplator() << 5;

Spójrz na koniec tej odpowiedzi dla lepszy przykład tego

Możesz Nie powiązać tymczasowe odniesienie z odniesieniem, ponieważ wygeneruje ono odniesienie do obiektu, który zostanie zniszczony na końcu wyrażenia, pozostawiając w ten sposób zwisające odniesienie (które jest niechlujne, a standard nie lubi niechlujnych).

Wartość zwracana przez ref() jest prawidłowym odniesieniem, ale metoda nie zwraca uwagi na długość życia zwracanego obiektu (ponieważ nie może mieć takiej informacji w jej kontekście). W zasadzie właśnie wykonałeś odpowiednik:

x& = const_cast<x&>(getX());

Powodem, dla którego można to zrobić z odniesieniem do obiektu tymczasowego jest to, że standard wydłuża żywotność obiektu tymczasowego do żywotności odniesienia, więc żywotność obiektów tymczasowych jest przedłużona poza koniec instrukcji.

Pozostaje więc tylko pytanie, dlaczego norma nie chce dopuścić do tego, aby odniesienie do tymczasowych rozszerzyło żywotność obiektu poza koniec oświadczenie?

Wydaje mi się, że jest to spowodowane tym, że utrudniłoby kompilatorowi uzyskanie poprawności dla obiektów tymczasowych. Zrobiono to dla const odwołań do tymczasowych, ponieważ ma to ograniczone zastosowanie, a tym samym zmusiło Cię do zrobienia kopii obiektu, aby zrobić cokolwiek użytecznego, ale zapewnia pewną ograniczoną funkcjonalność.

Pomyśl o tej sytuacji:

int getI() { return 5;}
int x& = getI();

x++; // Note x is an alias to a variable. What variable are you updating.

Wydłużenie żywotności tego tymczasowego obiektu będzie bardzo mylące.
Podczas gdy "po": {]}

int const& y = getI();

Da Ci kod, który jest intuicyjny w użyciu i zrozumiały.

Jeśli chcesz zmodyfikować wartość, powinieneś zwrócić wartość do zmiennej. Jeśli próbujesz uniknąć kosztów kopiowania objct z powrotem z funkcji (jak się wydaje, że obiekt jest kopiowany z powrotem (technicznie jest)). Więc nie przejmuj się kompilator jest bardzo dobry w 'Return Value Optimization'

 16
Author: Martin York,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:11

Dlaczego jest omówione w C++ FAQ (boldfacing mine):

W C++ odniesienia nie-const mogą wiązać się z lvalues, a odniesienia const mogą wiązać się z lvalues lub rvalue, ale nie ma nic, co mogłoby wiązać się z non-const rvalue. To jest, aby chronić ludzi przed zmianą wartości tymczasowych, które są niszczone, zanim ich nowa wartość będzie mogła być użyta . Na przykład:

void incr(int& a) { ++a; }
int i = 0;
incr(i);    // i becomes 1
incr(0);    // error: 0 is not an lvalue

Jeśli to incr (0) były dozwolone albo niektóre tymczasowe, że nikt nigdy nie widział zostanie zwiększona lub-co gorsza-wartość 0 będzie 1. To drugie brzmi głupio, ale tak naprawdę we wczesnych kompilatorach Fortrana pojawił się błąd, który odkładał Miejsce Pamięci do przechowywania wartości 0.

 7
Author: Tony Delroy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-30 10:05:29

Głównym problemem jest to, że

g(getx()); //error

Jest błędem logicznym: g modyfikuje wynik getx(), ale nie ma możliwości zbadania zmodyfikowanego obiektu. Jeśli g nie musiałby modyfikować swojego parametru, to nie wymagałby referencji lvalue, mógłby przyjąć parametr według wartości lub przez odniesienie const.

const X& x = getx(); // OK

Jest ważne, ponieważ czasami musisz ponownie użyć wyniku wyrażenia i jest całkiem jasne, że masz do czynienia z tymczasowym obiekt.

Jednak nie jest możliwe, aby

X& x = getx(); // error

Valid without making g(getx()) valid, czyli to, czego projektanci języka starali się uniknąć w pierwszej kolejności.

g(getx().ref()); //OK

Jest poprawna, ponieważ metody wiedzą tylko o const-ness this, nie wiedzą, czy są wywoływane na lvalue czy na rvalue.

Jak zawsze w C++, masz obejście tej reguły, ale musisz zasygnalizować kompilatorowi, że wiesz, co robisz, będąc explicit:

g(const_cast<x&>(getX()));
 6
Author: Dan Berindei,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-13 14:39:01

Wydaje się, że pierwotne pytanie dotyczące Dlaczego nie jest dozwolone zostało wyraźnie udzielone: "ponieważ jest to najprawdopodobniej błąd".

FWIW, pomyślałem, że pokażę Jak można to zrobić, mimo że nie sądzę, aby to była dobra technika.

Powodem, dla którego czasami chcę przekazać tymczasową metodę przyjmującą odwołanie non-const, jest celowe wyrzucenie wartości zwracanej przez-reference, na której metoda wywołująca nie ma znaczenia. Coś jak to:

// Assuming: void Person::GetNameAndAddr(std::string &name, std::string &addr);
string name;
person.GetNameAndAddr(name, string()); // don't care about addr

Jak wyjaśniono w poprzednich odpowiedziach, to się nie kompiluje. Ale to kompiluje i działa poprawnie (z moim kompilatorem):

person.GetNameAndAddr(name,
    const_cast<string &>(static_cast<const string &>(string())));

To po prostu pokazuje, że możesz użyć castingu, aby okłamać kompilator. Oczywiście, byłoby dużo czystsze zadeklarować i przekazać nieużywaną zmienną automatyczną:

string name;
string unused;
person.GetNameAndAddr(name, unused); // don't care about addr

Ta technika wprowadza niepotrzebną zmienną lokalną do zakresu metody. Jeśli z jakiegoś powodu chcesz zapobiec użyciu go później w metodzie, np. do unikaj zamieszania lub błędu, możesz go ukryć w lokalnym bloku:

string name;
{
    string unused;
    person.GetNameAndAddr(name, unused); // don't care about addr
}

-- Chris

 5
Author: Chris Pearson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-22 23:16:22

Dlaczego miałbyś chcieć X& x = getx();? Wystarczy użyć X x = getx(); i polegać na RVO.

 4
Author: DevFred,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-14 11:08:09

The evil workaround zawiera słowo kluczowe "mutable". Faktycznie bycie złym jest pozostawione jako ćwiczenie dla czytelnika. Lub zobacz tutaj: http://www.ddj.com/cpp/184403758

 4
Author: paul,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-14 15:02:10

Doskonałe pytanie, a oto moja próba bardziej zwięzłej odpowiedzi (ponieważ wiele przydatnych informacji jest w komentarzach i trudno wykopać w hałasie.)

Każde odniesienie związane bezpośrednio z tymczasowym wydłuży jego żywotność [12.2.5]. Z drugiej strony, odniesienie zainicjalizowane innym odniesieniem będzie , a nie (nawet jeśli jest to ostatecznie to samo tymczasowe). To ma sens (kompilator nie wie, do czego odnosi się to odniesienie).

Ale ten cały pomysł to bardzo mylące. Np. const X &x = X(); spowoduje, że czas tymczasowy będzie trwał tak długo, jak Referencja x, ale const X &x = X().ref(); nie będzie (kto wie, co ref() faktycznie wróci). W tym drugim przypadku Destruktor X zostanie wywołany na końcu tej linii. (Można to zaobserwować za pomocą nietrywialnego destruktora.)

Wydaje się więc ogólnie mylące i niebezpieczne (po co komplikować zasady dotyczące życia obiektów?), ale przypuszczalnie zaistniała potrzeba co najmniej referencji const, więc standard ten określa zachowanie dla nich.

[z SBI komentarz]: zauważ, że fakt, iż wiązanie go z referencją const zwiększa życie temporary jest wyjątkiem, który został dodany celowo (TTBOMK w celu umożliwienia ręcznej optymalizacji). Nie było wyjątek dodany dla odniesień nie-const, ponieważ Wiązanie tymczasowego do nie-const reference był postrzegany jako najprawdopodobniej programista błąd.

/ Align = "left" / pełna ekspresja. Aby jednak z nich skorzystać, potrzebujesz sztuczki, jaką masz z ref(). To legalne. Wydaje się, że nie ma dobrego powodu, aby przeskoczyć przez dodatkową obręcz, z wyjątkiem przypomnienia programiście, że dzieje się coś niezwykłego (mianowicie parametr odniesienia, którego modyfikacje zostaną szybko utracone).

[Inny SBI komentarz] powód, dla którego Stroustrup podaje (w D&E) zakaz wiązania rvalues to non-const reference is that, if Alexey ' s g () zmieni obiekt (którego można się spodziewać po funkcji przyjmującej non-const referencji), zmodyfikowałaby obiekt, który ma umrzeć, więc nikt może i tak uzyskać zmodyfikowaną wartość. Mówi, że to, większość prawdopodobnie jest to błąd.

 3
Author: DS.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:28

" jest jasne, że obiekt tymczasowy nie jest stały w powyższej próbce, ponieważ wywołuje do funkcji niestałych są dozwolone. Na przykład, ref() może modyfikować tymczasowe obiekt."

W twoim przykładzie getX() nie zwraca const X, więc możesz wywołać ref () w taki sam sposób jak X ().ref(). Zwracasz referencję non const i dlatego możesz wywoływać metody non const, czego nie możesz zrobić, to przypisać referencję do referencji non const.

Wraz z SadSidos skomentuj to sprawia, że Twoje trzy punkty są nieprawidłowe.

 2
Author: Patrick,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-14 11:41:25

Chciałbym podzielić się scenariuszem, w którym chciałbym zrobić to, o co prosi Alexey. W wtyczce Maya C++ muszę wykonać następujące czynności, aby uzyskać wartość do atrybutu node:

MFnDoubleArrayData myArrayData;
MObject myArrayObj = myArrayData.create(myArray);   
MPlug myPlug = myNode.findPlug(attributeName);
myPlug.setValue(myArrayObj);

Jest to żmudne do pisania, więc napisałem następujące funkcje pomocnicze:

MPlug operator | (MFnDependencyNode& node, MObject& attribute){
    MStatus status;
    MPlug returnValue = node.findPlug(attribute, &status);
    return returnValue;
}

void operator << (MPlug& plug, MDoubleArray& doubleArray){
    MStatus status;
    MFnDoubleArrayData doubleArrayData;
    MObject doubleArrayObject = doubleArrayData.create(doubleArray, &status);
    status = plug.setValue(doubleArrayObject);
}

A teraz mogę napisać kod Od początku posta jako:

(myNode | attributeName) << myArray;

Problem polega na tym, że nie kompiluje się poza Visual C++, ponieważ próbuje powiązać zmienną tymczasową zwracane z / operatora do referencji MPlug To mój scenariusz. Po prostu pomyślałem, że pokażę przykład, gdzie ktoś chciałby zrobić to, co Alexey opisać. Pozdrawiam wszystkich krytyki i sugestie!

Dzięki.
 1
Author: Robert Trussardi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-22 16:51:40