107 votes

Comment interdire les temporaires

Pour une classe Foo, il est un moyen de refuser la construire sans lui donner un nom?

Par exemple:

Foo("hi");

Et d'autoriser uniquement si vous lui donnez un nom, comme le suivant?

Foo my_foo("hi");

La durée de vie de la première est juste de l'énoncé, et le second est le bloc enfermant. Dans mon cas d'utilisation, Foo est la mesure du temps entre le constructeur et le destructeur. Depuis je n'ai jamais reportez-vous à la variable locale, j'oublie souvent de mettre, et accidentellement changement de la durée de vie. Je voudrais obtenir une erreur de compilation à la place.

101voto

ecatmur Points 64173

Une autre macro-solution basée sur:

#define Foo class Foo

L'énoncé Foo("hi"); s'étend à d' class Foo("hi");, ce qui est mal formé; mais Foo a("hi") s'étend à d' class Foo a("hi"), ce qui est correct.

Ceci a l'avantage qu'il est à la fois source et binaire-compatible avec l'existant (le bon) code. (Cette affirmation n'est pas tout à fait correcte - s'il vous plaît voir Johannes Schaub Commentaire et discussion ci-dessous: "Comment savez-vous qu'il est compatible avec le code existant? Son ami comprend sa tête et a void f() { int Foo = 0; } ce qui a été compilé fine et maintenant miscompiles! Aussi, chaque ligne définit une fonction membre de la classe Foo échoue: void class Foo::bar() {}")

45voto

user814628 Points 3299

Rendre le constructeur privé, mais donner la classe une méthode create .

25voto

Celle-ci n'entraîne pas une erreur de compilation, mais une erreur d'exécution. Au lieu de mesurer un mauvais moment, vous obtenez une exception qui peut être acceptable.

Tout constructeur, vous voulez besoins de la garde d'un argument par défaut sur lequel set(guard) est appelé.

struct Guard {
  Guard()
    :guardflagp()
  { }

  ~Guard() {
    assert(guardflagp && "Forgot to call guard?");
    *guardflagp = 0;
  }

  void *set(Guard const *&guardflag) {
    if(guardflagp) {
      *guardflagp = 0;
    }

    guardflagp = &guardflag;
    *guardflagp = this;
  }

private:
  Guard const **guardflagp;
};

class Foo {
public:
  Foo(const char *arg1, Guard &&g = Guard()) 
    :guard()
  { g.set(guard); }

  ~Foo() {
    assert(!guard && "A Foo object cannot be temporary!");
  }

private:
  mutable Guard const *guard;
}; 

Les caractéristiques sont les suivantes:

Foo f() {
  // OK (no temporary)
  Foo f1("hello");

  // may throw (may introduce a temporary on behalf of the compiler)
  Foo f2 = "hello";

  // may throw (introduces a temporary that may be optimized away
  Foo f3 = Foo("hello");

  // OK (no temporary)
  Foo f4{"hello"};

  // OK (no temporary)
  Foo f = { "hello" };

  // always throws
  Foo("hello");

  // OK (normal copy)
  return f;

  // may throw (may introduce a temporary on behalf of the compiler)
  return "hello";

  // OK (initialized temporary lives longer than its initializers)
  return { "hello" };
}

int main() {
  // OK (it's f that created the temporary in its body)
  f();

  // OK (normal copy)
  Foo g1(f());

  // OK (normal copy)
  Foo g2 = f();
}

Le cas de l' f2, f3 et le retour de l' "hello" peut-être pas voulu. Pour éviter de jeter, vous pouvez permettre à la source de la copie temporaire de, par la réinitialisation de l' guard maintenant garde nous au lieu de la source de la copie. Maintenant vous voyez pourquoi nous avons utilisé les pointeurs ci-dessus - cela nous permet d'être flexible.

class Foo {
public:
  Foo(const char *arg1, Guard &&g = Guard()) 
    :guard()
  { g.set(guard); }

  Foo(Foo &&other)
    :guard(other.guard)
  {
    if(guard) {
      guard->set(guard);
    }
  }

  Foo(const Foo& other)
    :guard(other.guard)
  {
    if(guard) {
      guard->set(guard);
    }
  }

  ~Foo() {
    assert(!guard && "A Foo object cannot be temporary!");
  }

private:
  mutable Guard const *guard;
}; 

Les caractéristiques de f2, f3 et return "hello" sont maintenant toujours // OK.

18voto

Kaz Points 18072

Il y A quelques années, j'ai écrit un patch pour le compilateur C++ de GNU qui ajoute une nouvelle option d'avertissement pour cette situation. Ceci est suivi dans un Bugzilla élément.

Malheureusement, GCC Bugzilla est un cimetière où le bien-considéré comme patch-inclus des suggestions de fonctionnalités vont mourir. :)

Cela a été motivé par le désir d'attraper exactement le genre de bugs qui font l'objet de cette question dans le code qui utilise des objets locaux que des gadgets de verrouillage et de déverrouillage, de mesurer le temps d'exécution et ainsi de suite.

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