105 votes

Durée de vie garantie du temporaire en C++ ?

Le langage C++ fournit-il une garantie pour la durée de vie d'une variable temporaire créée dans le cadre d'un appel de fonction mais non utilisée comme paramètre ? Voici un exemple de classe :

class StringBuffer
{
public:
    StringBuffer(std::string & str) : m_str(str)
    {
        m_buffer.push_back(0);
    }
    ~StringBuffer()
    {
        m_str = &m_buffer[0];
    }
    char * Size(int maxlength)
    {
        m_buffer.resize(maxlength + 1, 0);
        return &m_buffer[0];
    }
private:
    std::string & m_str;
    std::vector<char> m_buffer;
};

Et voici comment vous l'utiliseriez :

// this is from a crusty old API that can't be changed
void GetString(char * str, int maxlength);

std::string mystring;
GetString(StringBuffer(mystring).Size(MAXLEN), MAXLEN);

Quand le destructeur de l'objet temporaire StringBuffer sera-t-il appelé ? C'est ça :

  • Avant l'appel à GetString ?
  • Après le retour de GetString ?
  • Dépendant du compilateur ?

Je sais que le C++ garantit qu'une variable locale temporaire sera valide tant qu'il y aura une référence à celle-ci. Est-ce que cela s'applique aux objets parents lorsqu'il y a une référence à une variable membre ?

Gracias.

114voto

Johannes Schaub - litb Points 256113

Le destructeur pour ce type de temporaires est appelé à la fin de l'expression complète. C'est l'expression la plus externe qui ne fait partie d'aucune autre expression. Dans votre cas, c'est après le retour de la fonction et l'évaluation de la valeur. Donc, cela fonctionnera très bien.

C'est en fait ce qui fait fonctionner les modèles d'expression : Ils peuvent garder des références à ce genre de temporaires dans une expression comme

e = a + b * c / d

Parce que chaque temporaire durera jusqu'à l'expression

x = y

est évalué complètement. Il est décrit de manière très concise dans 12.2 Temporary objects dans la norme.

19voto

Rick Points 2477

La réponse de litb est exacte. La durée de vie de l'objet temporaire (également connu sous le nom de rvalue) est liée à l'expression et le destructeur de l'objet temporaire est appelé à la fin de l'expression complète. Lorsque le destructeur de StringBuffer est appelé, le destructeur de m_buffer sera également appelé, mais pas le destructeur de m_str puisqu'il s'agit d'une référence.

Notez que C++0x change un peu les choses car il ajoute les références rvalue et la sémantique de déplacement. Essentiellement, en utilisant un paramètre de référence rvalue (noté avec &&), je peux "déplacer" la rvalue dans la fonction (au lieu de la copier) et la durée de vie de la rvalue peut être liée à l'objet dans lequel elle est déplacée, et non à l'expression. Il existe un très bon L'équipe MSVC a publié un article sur son blog, qui explique tout cela en détail. et j'encourage les gens à le lire.

L'exemple pédagogique pour déplacer les rvalues est celui des chaînes de caractères temporaires et je vais montrer l'affectation dans un constructeur. Si j'ai une classe MyType qui contient une variable membre string, elle peut être initialisée avec une rvalue dans le constructeur comme ceci :

class MyType{
   const std::string m_name;
public:
   MyType(const std::string&& name):m_name(name){};
}

C'est bien parce que lorsque je déclare une instance de cette classe avec un objet temporaire :

void foo(){
    MyType instance("hello");
}

ce qui se passe, c'est que nous évitons de copier et de détruire l'objet temporaire et que "hello" est placé directement dans la variable membre de l'instance de la classe propriétaire. Si l'objet est plus lourd qu'une "chaîne", la copie supplémentaire et l'appel au destructeur peuvent être significatifs.

-Rick

4voto

David Segonds Points 25602

Après le retour de l'appel à GetString.

4voto

Daniel Earwicker Points 63298

J'ai écrit presque exactement la même classe :

template <class C>
class _StringBuffer
{
    typename std::basic_string<C> &m_str;
    typename std::vector<C> m_buffer;

public:
    _StringBuffer(std::basic_string<C> &str, size_t nSize)
        : m_str(str), m_buffer(nSize + 1) { get()[nSize] = (C)0; }

    ~_StringBuffer()
        { commit(); }

    C *get()
        { return &(m_buffer[0]); }

    operator C *()
        { return get(); }

    void commit()
    {
        if (m_buffer.size() != 0)
        {
            size_t l = std::char_traits<C>::length(get());
            m_str.assign(get(), l);    
            m_buffer.resize(0);
        }
    }

    void abort()
        { m_buffer.resize(0); }
};

template <class C>
inline _StringBuffer<C> StringBuffer(typename std::basic_string<C> &str, size_t nSize)
    { return _StringBuffer<C>(str, nSize); }

Avant la norme, chaque compilateur procédait différemment. Je crois que l'ancien Manuel de référence annoté du C++ spécifiait que les temporaires devaient être nettoyés à la fin de la portée, et certains compilateurs le faisaient. En 2003, j'ai découvert que ce comportement existait toujours par défaut sur le compilateur Forte C++ de Sun, donc StringBuffer ne fonctionnait pas. Mais je serais étonné qu'un compilateur actuel soit toujours aussi cassé.

3voto

BigSandwich Points 1372

StringBuffer est dans la portée de GetString. Il devrait être détruit à la fin de la portée de GetString (c'est-à-dire quand il revient). De plus, je ne crois pas que le C++ garantisse l'existence d'une variable tant qu'il y a une référence.

Ce qui suit devrait être compilé :

Object* obj = new Object;
Object& ref = &(*obj);
delete obj;

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X