Как запретить временные

Для класса Foo существует ли способ запретить его создание без указания имени?

Например:

Foo("hi");

И только разрешите это, если вы укажете ему имя, например следующее:

Foo my_foo("hi");

Время жизни первого - это просто оператор, а второй - охватывающий блок. В моем случае использования Foo измеряет время между конструктором и деструктором. Поскольку я никогда не ссылаюсь на локальную переменную, я часто забываю ее вставить и случайно изменить всю жизнь. Я хотел бы получить ошибку времени компиляции.

Ответ 1

Другое решение на основе макросов:

#define Foo class Foo

Утверждение Foo("hi"); расширяется до class Foo("hi");, которое плохо сформировано; но Foo a("hi") расширяется до class Foo a("hi"), что верно.

Это имеет то преимущество, что оно совместимо как с исходным, так и с двоичным кодом с существующим (правильным) кодом. (Это утверждение не совсем корректно - см. Johannes Schaub Комментарий и последующее обсуждение ниже: "Как вы можете знать, что источник совместим с существующим кодом? Его друг включает свой заголовок и имеет void f() { int Foo = 0;}, который ранее скомпилировался отлично и теперь ошибочно уничтожает! Кроме того, каждая строка, которая определяет функцию-член класса Foo, терпит неудачу: void class Foo:: bar() {}" )

Ответ 2

Как насчет маленького взлома

class Foo
{
    public:
        Foo (const char*) {}
};

void Foo (float);


int main ()
{
    Foo ("hello"); // error
    class Foo a("hi"); // OK
    return 1;
}

Ответ 3

Сделать конструктор приватным, но дать классу метод create.

Ответ 4

Это не приводит к ошибке компилятора, а ошибке во время выполнения. Вместо того, чтобы измерять неправильное время, вы получаете исключение, которое также может быть приемлемым.

Любой конструктор, который вы хотите защитить, требует аргумента по умолчанию, по которому вызывается set(guard).

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;
}; 

Характеристики:

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 f that created the temporary in its body)
  f();

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

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

Случай f2, f3 и возврат "hello" может не потребоваться. Чтобы предотвратить выброс, вы можете позволить источнику копии быть временным, сбросив guard, чтобы теперь охранять нас вместо источника копии. Теперь вы также видите, почему мы использовали указатели выше - это позволяет нам быть гибкими.

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;
}; 

Характеристики для f2, f3 и для return "hello" теперь всегда // OK.

Ответ 5

Несколько лет назад я написал патч для компилятора GNU С++, который добавляет новый вариант предупреждения для этой ситуации. Это отслеживается в элемент Bugzilla.

К сожалению, GCC Bugzilla - это место погребения, где хорошо продуманные предложения с включенными патчами идут на ум.:)

Это было вызвано желанием поймать именно такие ошибки, которые являются предметом этого вопроса в коде, который использует локальные объекты в качестве гаджетов для блокировки и разблокировки, измерения времени выполнения и т.д.

Ответ 6

Как и в случае с вашей реализацией, вы не можете этого сделать, но вы можете использовать это правило в своих интересах:

Временные объекты не могут быть привязаны к неконстантным ссылкам

Вы можете переместить код из класса в автономную функцию, которая принимает неконстантный опорный параметр. Если вы это сделаете, вы получите ошибку компилятора, если временная попытка привязки к неконстантной ссылке.

Пример кода

class Foo
{
    public:
        Foo(const char* ){}
        friend void InitMethod(Foo& obj);
};

void InitMethod(Foo& obj){}

int main()
{
    Foo myVar("InitMe");
    InitMethod(myVar);    //Works

    InitMethod("InitMe"); //Does not work  
    return 0;
}

Выход

prog.cpp: In function ‘int main()’:
prog.cpp:13: error: invalid initialization of non-const reference of type ‘Foo&’ from a temporary of type ‘const char*’
prog.cpp:7: error: in passing argument 1 of ‘void InitMethod(Foo&)’

Ответ 7

Просто не имеет конструктора по умолчанию и требуется ссылка на экземпляр в каждом конструкторе.

#include <iostream>
using namespace std;

enum SelfRef { selfRef };

struct S
{
    S( SelfRef, S const & ) {}
};

int main()
{
    S a( selfRef, a );
}

Ответ 8

Нет, боюсь, это невозможно. Но вы можете получить тот же эффект, создав макрос.

#define FOO(x) Foo _foo(x)

С этим на месте вы можете просто написать FOO (x) вместо Foo my_foo (x).

Ответ 9

Поскольку основной целью является предотвращение ошибок, рассмотрим следующее:

struct Foo
{
  Foo( const char* ) { /* ... */ }
};

enum { Foo };

int main()
{
  struct Foo foo( "hi" ); // OK
  struct Foo( "hi" ); // fail
  Foo foo( "hi" ); // fail
  Foo( "hi" ); // fail
}

Таким образом, вы не можете забыть назвать переменную, и вы не можете забыть написать struct. Подробный, но безопасный.

Ответ 10

Объявить однопараметрический конструктор как явный, и никто никогда не будет создавать объект этого класса непреднамеренно.

Например

class Foo
{
public: 
  explicit Foo(const char*);
};

void fun(const Foo&);

может использоваться только таким образом

void g() {
  Foo a("text");
  fun(a);
}

но так никогда (через временный стек)

void g() {
  fun("text");
}

См. также: Alexandrescu, стандарты кодирования С++, пункт 40.