Question Astuce pour s'assurer que "seulement" des valeurs peuvent exister d'une classe particulière?


Existe-t-il une astuce en C ++ qui peut garantir qu'un utilisateur d'une classe ne peut générer que des valeurs?

Exemple:

struct PoorClass { /* ... */};

struct EnrichedClass {
    explicit EnrichedClass (const PoorClass & poor)
        : m_poor (poor)
    {
    }

    /* additional functionality for poor objects */

private:
    const PoorClass & m_poor;
}

const EnrichedClass asEnriched (const PoorClass & poor)
{
    return EnrichedClass { poor };
}

Désormais, les objets enrichis ne devraient être que temporaires, car ils ne devraient pas survivre aux objets pauvres emballés. Idéalement, les objets enrichis ne devraient jamais être stockés dans une variable, mais seulement transmis en tant que références à des fonctions.

Existe-t-il un moyen d’assurer que, c’est-à-dire, que cela soit aussi sûr que possible?


13
2017-07-23 08:23


origine


Réponses:


Vous ne pouvez fournir que des méthodes de valeur r pour struct EnrichedClass:

struct EnrichedClass {
    explicit EnrichedClass (const PoorClass& poor) : m_poor (poor) {}

    /* additional functionality for poor objects */

    void foo() &&; // note the && at the end.


private:
    const PoorClass & m_poor;
};

4
2017-07-23 08:42



En fin de compte, il est impossible de réaliser directement la tâche souhaitée.

Je peux penser à une façon de «contourner le problème»:

Créez un constructeur privé ou protégé et copiez un constructeur avec une fonction de construction statique nommée.

Quelque chose du genre:

struct PoorClass { /* ... */ };

struct EnrichedClass
{
  EnrichedClass& operator= (const EnrichedClass & enr) = delete;
private:
  explicit EnrichedClass(const PoorClass & poor)
    : m_poor(poor)
  { }
  EnrichedClass(const EnrichedClass & enr)
    : m_poor(enr.m_poor)
  { }

public:
  static EnrichedClass enrich(const PoorClass & poor)
  {
    return EnrichedClass{ poor };
  }
  void stuff() { }
private:
  const PoorClass & m_poor;
};

Ainsi, on ne peut pas copier l'instance:

EnrichedClass::enrich(p).stuff(); // works
auto ec = EnrichedClass::enrich(p); // does not

Note: On peut encore avoir des lvalues ​​de EnrichedClass via des références rvalue.

void foo(EnrichedClass && e)
{
  // e = lvalue here
}

À propos de l'idée de qualification rvalue-ref:

void bar() && { /* do stuff */ }

Si vous voulez passer votre valeur à une fonction en utilisant les références rvalue, vous ne pouvez pas appeler la fonction membre avec && ref qualification puisque les références de rvalue sont encore des lvalues ​​(c.-à-d. e n'est pas une valeur à l'intérieur foo au dessus).

Vous pourriez faire:

void foo(EnrichedClass && e)
{
  std::move(e).bar();
}

Mais maintenant vous avez fourni une garantie pour bar: L'objet référencé par this est une valeur Il est bon de faire ce que vous voulez tant que l’objet est dans un état valide (mais non spécifié) après l’appel (c.-à-d. bar peut passer de this).

Ainsi, après avoir appelé bar vous ne pouvez pas connaître l'état de e.


4
2017-07-23 09:08



On dirait que vous pouvez faire le tour avec:

  • avoir des constructeurs privés dans EnrichedClass
  • faire asEnriched(PoorClass) renvoyer une EnrichedClass &&

Voici un exemple :

#include <iostream>

struct PoorClass {
    // trace construction and destruction
    PoorClass() {
        std::cerr << "Create " << this << std::endl;
    }
    ~PoorClass() {
        std::cerr << "Destroy " << this << std::endl;
    }
};

struct EnrichedClass {
private:
    EnrichedClass (const PoorClass & poor)
        : m_poor (poor)
    {
    }
    EnrichedClass(const EnrichedClass& rich): m_poor(rich.m_poor){}

    /* additional functionality for poor objects */

    const PoorClass & m_poor;

    friend const EnrichedClass&& asEnriched (const PoorClass & poor);
    friend void dump(const EnrichedClass& rich); // only to be able to access m_poor
};

const EnrichedClass&& asEnriched (const PoorClass & poor)
{
    return EnrichedClass ( poor ); // gives a warning, but seems harmless
          // provided the ref in only used as a parameter to a function call
          // of course don't affect it to a true ref, or do not take the address
          //  if you do not want dangling refs or pointers !
}

void dump(const EnrichedClass & rich) {
    std::cerr << "Dump " << &(rich.m_poor) << std::endl;
}

int main() {
   PoorClass poor;
   // EnrichedClass rich = poor; causes an error
   // EnrichedClass rich = asEnriched(poor); causes an e};
   dump(asEnriched(PoorClass()));
   std::cerr << "End" << std::endl;
   return 0;
}

Cela affiche:

Create 0xbfbfec58
Create 0xbfbfec50
Dump 0xbfbfec50
Destroy 0xbfbfec50
end
Destroy 0xbfbfec58

ce qui signifie que la référence est valide pour la durée de l'appel de vidage alors qu'il est impossible d'affecter un EnrichedClass à une variable.

Bien sûr, vous pourriez affecter une référence. Par exemple const EnrichedClass& ref = asEnriched(poor); serait accepté, mais vous donnera une réf. Je ne peux pas imaginer un moyen de forcer le compilateur à détecter cela comme une erreur - après tout, il a déjà donné un avertissement pour asEnriched renvoyer une référence à un temporaire ...


3
2017-07-23 09:37



Pas sûr de la question, pas sûr de ma réponse. mais je pense que vous pouvez y parvenir en cachant Poor à l'intérieur Enriched et surcharger le Poor opérateur:

class Better{
    class Poor{};
    Poor& m_Poor;

public:

    Better(class Poor& poor) :m_Poor(poor){}
    operator Poor(){
        return m_Poor;
    }

};

vous ne pouvez pas créer directement Poor car c'est privé. vous ne pouvez fournir que de la valeur R et passer Better comme Poor car il a un opérateur de conversion.


1
2017-07-23 09:48