c++ attributes - В чем смысл нореткин?




20 contracts (5)

[dcl.attr.noreturn] предоставляет следующий пример:

[[ noreturn ]] void f() {
    throw "error";
    // OK
}

но я не понимаю, в чем смысл [[noreturn]] , потому что возвращаемый тип функции уже void .

Итак, в чем noreturn атрибута noreturn ? Как он должен использоваться?


Answers

Тип теоретически говоря, void - это то, что называется в других unit или top других языков. Его логический эквивалент - True . Любое значение может быть законно отбрасываться в void (каждый тип является подтипом void ). Подумайте об этом как о «вселенной»; нет общих операций со всеми значениями в мире, поэтому нет действительных операций над значением типа void . Положите это другим способом, говоря вам, что что-то принадлежит набору вселенной, не дает вам никакой информации - вы уже знаете это. Так звучит следующее:

(void)5;
(void)foo(); // whatever foo() does

Но присваивание ниже:

void raise();
void f(int y) {
    int x = y!=0 ? 100/y : raise(); // raise() returns void, so what should x be?
    cout << x << endl;
}

[[noreturn]] , с другой стороны, называется иногда empty , Nothing , Bottom или Bot и является логическим эквивалентом False . Он вообще не имеет значений, и выражение этого типа может быть передано (т. Е. Подтип) любого типа. Это пустой набор. Обратите внимание, что если кто-то скажет вам, что значение выражения foo () принадлежит пустому набору « очень информативно», он сообщает вам, что это выражение никогда не завершит его нормальное выполнение; он будет прервать, бросить или повесить. Это полная противоположность void .

Итак, следующее не имеет смысла (псевдо-C ++, поскольку noreturn не является первоклассным C ++-типом)

void foo();
(noreturn)5; // obviously a lie; the expression 5 does "return"
(noreturn)foo(); // foo() returns void, and therefore returns

Но присваивание ниже совершенно законно, поскольку компилятор понимает, что компилятор не возвращает:

void f(int y) {
    int x = y!=0 ? 100/y : throw exception();
    cout << x << endl;
}

В идеальном мире вы можете использовать noreturn в качестве возвращаемого значения функции raise() выше:

noreturn raise() { throw exception(); }
...
int x = y!=0 ? 100/y : raise();

К сожалению, C ++ не позволяет этого, вероятно, по практическим соображениям. Вместо этого он дает вам возможность использовать [[ noreturn ]] который помогает определять оптимизацию и предупреждения компилятора.


Это означает, что функция не будет завершена. Поток управления никогда не удастся выполнить оператор после вызова f() :

void g() {
   f();
   // unreachable:
   std::cout << "No! That's impossible" << std::endl;
}

Информация может использоваться компилятором / оптимизатором по-разному. Компилятор может добавить предупреждение о том, что приведенный выше код недоступен, и он может модифицировать фактический код g() различными способами, например, для поддержки продолжений.


Атрибут noreturn должен использоваться для функций, которые не возвращаются вызывающему. Это не означает, что функции void (которые возвращают вызывающему абоненту - они просто не возвращают значение), а функции, в которых поток управления не возвращается к вызывающей функции после завершения функции (например, функции, выходящие из приложения, цикл навсегда или исключить исключения, как в вашем примере).

Это может быть использовано компиляторами для создания некоторых оптимизаций и генерации более эффективных предупреждений. Например, если f имеет атрибут noreturn, компилятор может предупредить вас о том, что g() является мертвым кодом при записи f(); g(); f(); g(); , Точно так же компилятор будет знать, чтобы не предупреждать вас о отсутствующих операторах return после вызовов f() .


Предыдущие ответы правильно объяснили, что такое noreturn, но не почему это существует. Я не думаю, что комментарии «оптимизации» являются основной целью: функции, которые не возвращаются, редки и обычно не нуждаются в оптимизации. Скорее, я думаю, что основным смыслом норэртнуна является предотвращение ложноположительных предупреждений. Например, рассмотрите этот код:

int f(bool b){
    if (b) {
        return 7;
    } else {
        abort();
    }
 }

Если бы abort () не был помечен как «noreturn», компилятор мог бы предупредить об этом коде, имеющем путь, где f не возвращает целое число, как ожидалось. Но из-за того, что abort () не возвращается, он знает, что код верен.


Умный указатель - это класс, который обертывает указатель C ++ «raw» (или «голый») для управления временем жизни объекта, на который указывают. Существует не один тип интеллектуального указателя, но все они пытаются абстрагировать необработанный указатель на практике.

Смарт-указатели должны быть предпочтительнее, чем исходные указатели. Если вы считаете, что вам нужно использовать указатели (сначала подумайте, действительно ли вы это делаете), вы обычно хотели бы использовать интеллектуальный указатель, поскольку это может облегчить многие проблемы с необработанными указателями, в основном забывая удалить объект и утечку памяти.

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

// Need to create the object to achieve some goal
MyObject* ptr = new MyObject(); 
ptr->DoSomething(); // Use the object in some way
delete ptr; // Destroy the object. Done with it.
// Wait, what if DoSomething() raises an exception...?

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

SomeSmartPtr<MyObject> ptr(new MyObject());
ptr->DoSomething(); // Use the object in some way.

// Destruction of the object happens, depending 
// on the policy the smart pointer class uses.

// Destruction would happen even if DoSomething() 
// raises an exception

Простейшая используемая политика включает в себя область объекта оболочки интеллектуального указателя, например, реализована boost::scoped_ptr или std::unique_ptr .

void f()
{
    {
       boost::scoped_ptr<MyObject> ptr(new MyObject());
       ptr->DoSomethingUseful();
    } // boost::scopted_ptr goes out of scope -- 
      // the MyObject is automatically destroyed.

    // ptr->Oops(); // Compile error: "ptr" not defined
                    // since it is no longer in scope.
}

Обратите внимание, что экземпляры scoped_ptr не могут быть скопированы. Это предотвращает удаление указателя несколько раз (неправильно). Тем не менее, вы можете передавать ссылки на другие функции, которые вы вызываете.

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

Более сложная политика интеллектуальных указателей включает ссылку, подсчитывающую указатель. Это позволяет скопировать указатель. Когда последняя «ссылка» на объект уничтожается, объект удаляется. Эта политика реализуется boost::shared_ptr и std::shared_ptr .

void f()
{
    typedef std::shared_ptr<MyObject> MyObjectPtr; // nice short alias
    MyObjectPtr p1; // Empty

    {
        MyObjectPtr p2(new MyObject());
        // There is now one "reference" to the created object
        p1 = p2; // Copy the pointer.
        // There are now two references to the object.
    } // p2 is destroyed, leaving one reference to the object.
} // p1 is destroyed, leaving a reference count of zero. 
  // The object is deleted.

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

Существует один недостаток ссылок на подсчитанные указатели - возможность создания ссылки:

// Create the smart pointer on the heap
MyObjectPtr* pp = new MyObjectPtr(new MyObject())
// Hmm, we forgot to destroy the smart pointer,
// because of that, the object is never destroyed!

Другая возможность заключается в создании круговых ссылок:

struct Owner {
   boost::shared_ptr<Owner> other;
};

boost::shared_ptr<Owner> p1 (new Owner());
boost::shared_ptr<Owner> p2 (new Owner());
p1->other = p2; // p1 references p2
p2->other = p1; // p2 references p1

// Oops, the reference count of of p1 and p2 never goes to zero!
// The objects are never destroyed!

Чтобы обойти эту проблему, как Boost, так и C ++ 11 определили weak_ptr чтобы определить слабую (бесчисленную) ссылку на shared_ptr .

ОБНОВИТЬ

Этот ответ довольно старый, и поэтому описывает, что было «хорошим» в то время, что было умными указателями, предоставляемыми библиотекой Boost. Начиная с C ++ 11, стандартная библиотека предоставила достаточные типы интеллектуальных указателей, поэтому вам следует использовать std::unique_ptr , std::shared_ptr и std::weak_ptr .

Существует также std::auto_ptr . Он очень похож на указатель с областью действия, за исключением того, что он также имеет «специальную» опасную способность, которую можно скопировать, что также неожиданно передает право собственности! Он устарел в новейших стандартах, поэтому вы не должны его использовать. Вместо этого используйте std::unique_ptr .

std::auto_ptr<MyObject> p1 (new MyObject());
std::auto_ptr<MyObject> p2 = p1; // Copy and transfer ownership. 
                                 // p1 gets set to empty!
p2->DoSomething(); // Works.
p1->DoSomething(); // Oh oh. Hopefully raises some NULL pointer exception.




c++ c++11 attributes noreturn