Domanda

Con il seguente codice, "Hello2" non viene visualizzato quando la stringa temporanea creata sulla riga 3 stampi prima che venga eseguita la riga 4. L'uso di un #define come sulla riga 1 evita questo problema, ma c'è un modo per evitare questo problema senza usare #define? (Il codice C ++ 11 va bene)

#include <iostream>
#include <string>

class C
{
public:
  C(const std::string& p_s) : s(p_s) {}
  const std::string& s;
};

int main()
{
  #define x1 C(std::string("hello1")) // Line 1
  std::cout << x1.s << std::endl; // Line 2

  const C& x2 = C(std::string("hello2")); // Line 3
  std::cout << x2.s << std::endl; // Line 4
}

Una precisazione:

Nota che credo che Boost Ublas negozi riferimenti, ecco perché non voglio archiviare una copia. Se suggerisci di archiviare per valore, spiega perché aumentare gli Ublas è sbagliato e memorizzare in valore non influirà sulle prestazioni.

È stato utile?

Soluzione

I modelli di espressione che archiviano per riferimento in genere lo fanno per le prestazioni, ma con l'avvertimento vengono usati solo come temporanei

Preso dal documentazione di boost.proto (che può essere utilizzato per creare modelli di espressione):

Nota Un lettore astuto noterà che l'oggetto y definito sopra verrà lasciato con un riferimento penzolante a un int temporaneo. Nel tipo di applicazioni ad alte prestazioni. . Proto fornisce utilità per gli alberi di espressione profonda in modo che possano essere passati come tipi di valore senza preoccupazione per i riferimenti pendenti.

Nel tuo esempio iniziale questo significa che dovresti fare:

std::cout << C(std::string("hello2")).s << std::endl;

In questo modo il C temporaneo mai sopravvive al std::string temporaneo. In alternativa potresti fare s Un membro non di riferimento come ha sottolineato altri.

Da quando menzioni C ++ 11, in futuro mi aspetto che gli alberi di espressione archiviano per valore, usando la semantica di mossa per evitare costose copie e involucri come std :: reference_wrapper per dare ancora la possibilità di archiviare per riferimento. Questo avrebbe giocato bene con auto.

Una possibile versione C ++ 11 del tuo codice:

class C
{
public:
    explicit
    C(std::string const& s_): s { s_ } {}

    explicit
    C(std::string&& s_): s { std::move(s_) } {}

    std::string const&
    get() const& // notice lvalue *this
    { return s; }

    std::string
    get() && // notice rvalue *this
    { return std::move(s); }

private:
    std::string s; // not const to enable moving
};

Ciò significherebbe quel codice come C("hello").get() Assetterebbe la memoria solo una volta, ma avrebbe comunque giocato bene con

std::string clvalue("hello");
auto c = C(clvalue);
std::cout << c.get() << '\n'; // no problem here

Altri suggerimenti

Ma c'è un modo per evitare questo problema senza usare #define?

Sì.

Definisci la tua classe come: (Non archiviare il riferimento)

class C
{
public:
  C(const std::string & p_s) : s(p_s) {}
  const std::string s; //store the copy!
};

Memorizza la copia!

Demo: http://www.ideone.com/gpsa2


Il problema con il tuo codice è quello std::string("hello2") crea un temporaneo e rimane vivo fintanto che sei nel costruttore di C, e successivamente il temporaneo viene distrutto ma il tuo oggetto x2.s Stills indica (l'oggetto morto).

Dopo la tua modifica:

La memorizzazione per riferimento è pericoloso ed errori incline a volte. Dovresti farlo solo quando sei sicuro al 100% che il riferimento variabile non andrà mai fuori dalla portata fino alla sua morte.

C ++ string è molto ottimizzato. Fino a quando non si modifica un valore di stringa, tutto si riferirà solo alla stessa stringa. Per testarlo, puoi sovraccaricare operator new (size_t) e inserisci una dichiarazione di debug. Per più copie della stessa stringa, vedrai che l'allocazione della memoria avverrà solo una volta.

La definizione della classe non dovrebbe essere archiviata per riferimento, ma per valore come,

class C {
  const std::string s;  // s is NOT a reference now
};

Se questa domanda è pensata per il senso generale (non specifico per la stringa), il modo migliore è utilizzare l'allocazione dinamica.

class C {
  MyClass *p;
  C() : p (new MyClass()) {}  // just an example, can be allocated from outside also
 ~C() { delete p; }
};

Senza guardare a BLAS, i modelli di espressione in genere fanno un uso intenso di oggetti temporanei di tipi che non dovresti nemmeno sapere che esista. Se Boost sta archiviando riferimenti come questo all'interno dei loro, allora subirebbero lo stesso problema che vedi qui. Ma fintanto che quegli oggetti temporanei rimangono temporanei e l'utente non li memorizza per dopo, tutto va bene perché i temporari a cui fanno riferimento rimangono vivi fintanto che lo fanno gli oggetti temporanei. Il trucco è che si esegue una copia profonda quando l'oggetto intermedio viene trasformato nell'oggetto finale che l'utente memorizza. Hai saltato questo ultimo passaggio qui.

In breve, è una mossa pericolosa, che è perfettamente sicura fintanto che l'utente della tua biblioteca non fa nulla di sciocco. Non consiglierei di farne uso a meno che tu non abbia un chiaro bisogno e sei ben consapevole delle conseguenze. E anche allora, potrebbe esserci un'alternativa migliore, non ho mai lavorato con modelli di espressione in alcuna capacità seria.

A parte questo, da quando hai taggato questo C ++ 0x, auto x = a + b; Sembra che sarebbe una di quelle cose "sciocche" che gli utenti del tuo codice possono fare per rendere pericolosa la tua ottimizzazione.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top