2016-09-05 1 views
23

Ich versuche festzustellen, ob eine C++ - Funktion so deklariert werden kann, dass der Rückgabewert nicht ignoriert werden kann (idealerweise zur Kompilierzeit erkannt). Ich habe versucht, eine Klasse mit einer private (oder in C++ 11, delete d) operator void() zu deklarieren, um zu versuchen, die implizite Konvertierung zu void zu fangen, wenn ein Rückgabewert nicht verwendet wird.Kann eine C++ - Funktion so deklariert werden, dass der Rückgabewert nicht ignoriert werden kann?

Hier ist ein Beispiel-Programm:

class Unignorable { 
    operator void(); 
}; 

Unignorable foo() 
{ 
    return Unignorable(); 
} 

int main() 
{ 
    foo(); 
    return 0; 
} 

Leider ist mein Compiler (Klirren-703.0.31) sagt:

test.cpp:2:5: warning: conversion function converting 'Unignorable' to 'void' will never be used 
    operator void(); 
    ^

und erheben keinen Fehler oder eine Warnung auf dem Aufruf von foo() . Also, das wird nicht funktionieren. Gibt es einen anderen Weg, dies zu tun? Antworten speziell für C++ 11 oder C++ 14 oder später wären in Ordnung.

+0

Laufzeit oder Kompilierzeit? –

+6

http://stackoverflow.com/questions/12692892/force-returned-object-to-be-assigned –

+1

Sind Sie sicher, dass eine implizite Konvertierung in "void" vorliegt? Ich kann nichts im Standard darüber finden. (Aber ich bin nicht einmal sicher, was konvertiert werden würde.) AFAIK, alles was passiert, wenn der Rückgabewert keinen "Empfänger" hat, ist die Konstruktion und Zerstörung eines temporären Objekts. – molbdnilo

Antwort

8

, um von anderen Antworten zusammenfassen & Kommentare, im Grunde haben Sie 3 Möglichkeiten:

  1. Get C++ 17 sein fähig zu verwenden [[nodiscard]]
  2. In g ++ (auch clang ++), verwenden Sie Compiler-Erweiterungen wie __wur (definiert als __attribute__ ((__warn_unused_result__))), oder das tragbarere Attribut (C++ 11 und höher) [[gnu::warn_unused_result]].
  3. Verwenden Laufzeitprüfungen das Problem während der Unit-Tests

Wenn alle diese 3 nicht möglich zu fangen, dann gibt es eine weitere Möglichkeit, die Art von „Negative Compilieren“ ist. Definieren Sie Ihre Unignorable wie folgt:

struct Unignorable { 
    Unignorable() = default; 
#ifdef NEGATIVE_COMPILE 
    Unignorable (const Unignorable&) = delete; // C++11 
    Unignorable& operator= (const Unignorable&) = delete; 
    //private: Unignorable (const Unignorable&); public: // C++03 
    //private: Unignorable& operator= (const Unignorable&); public: // C++03 
    /* similar thing for move-constructor if needed */ 
#endif 
}; 

Jetzt mit -DNEGATIVE_COMPILE oder gleichwertig in anderen Compilern wie MSVC kompilieren.Es gibt Fehler bei, wo immer das Ergebnis nicht ignoriert:

auto x = foo(); // error 

Es wird jedoch keine Störungen geben, wo immer das Ergebnis ignoriert wird:

foo(); // no error 

Mit jedem modernen Code-Browser (wie Eclipse-CDT), können Sie alle Vorkommen von foo() finden und diese Orte korrigieren, die keinen Fehler ergaben. Entfernen Sie in der neuen Kompilierung einfach das vordefinierte Makro für "NEGATIVE_COMPILE".

Dies könnte etwas besser sein im Vergleich zu einfach zu finden foo() und Überprüfung auf die Rückkehr, weil es viele Funktionen wie foo(), wo Sie nicht wollen, um den Rückgabewert zu ignorieren.

Dies ist etwas mühsam, aber wird für alle Versionen von C++ mit allen Compilern funktionieren.

5

Vor C++ 17 dieser Ansatz in den Sinn kam:

#include <stdexcept> 
#include <exception> 
#include <boost/optional.hpp> 

// proxy object which complains if it still owns the return 
// value when destroyed 
template<class T> 
struct angry 
{ 
    angry(T t) : value_(std::move(t)) {} 
    angry(angry&&) = default; 
    angry(angry const&) = default; 
    angry& operator=(angry&&) = default; 
    angry& operator=(angry const&) = default; 

    ~angry() noexcept(false) 
    { 
    if (value_) throw std::logic_error("not used"); 
    } 

    T get() && { 
    T result = std::move(value_).value(); 
    value_.reset(); 
    return result; 
    } 

    boost::optional<T> value_; 
}; 

// a function which generates an angry int  
angry<int> foo() 
{ 
    return 10; 
} 

int main() 
{ 
    // obtain an int 
    auto a = foo().get(); 

    // this will throw 
    foo(); 
} 

Synopsis: statt ein T zurückkehren, eine Funktion eine angry<T> kehrt die den Anrufer bestrafen, indem sie einen logic_error werfen, wenn der Wert nicht ist extrahiert vor der Zerstörung.

Es ist eine Laufzeit-Lösung, die eine Einschränkung ist, aber sollte zumindest früh in Komponententests erwischt werden.

Ein schlauer Benutzer kann es natürlich unterlaufen:

foo().get(); // won't throw 
+0

Das ist ein interessanter Ansatz und ich habe ihn upvoted, aber idealerweise suche ich nach einer Kompilierzeitlösung. Ich bin nicht besorgt über die Fähigkeit für Anrufer, es zu unterwandern, weil sie immer können, indem Sie das Ergebnis etwas zuweisen und diesen Wert dann einfach nicht verwenden. –

+0

@GregHewgill dann denke ich, du bist Pech bis C++ 17, Sperren Compiler-Erweiterungen –

6

__attribute__ ((warn_unused_result)) See.

int foo() __attribute__ ((warn_unused_result)); 
int foo(){return 123;} 

int main() 
{ 
    foo(); //compiler warning 
    auto i = foo(); //valid 
} 

Dann die Warnung zwingen, ein Fehler zu sein:

clang++ -std=c++1z -Werror="unused-result" 
+0

In G ++ (wahrscheinlich clang ++ auch), können wir auch '__wur' als Kurzschrift-Makro für' __attribute__ ((__warn_unused_result __)) ' . Dieses Makro wird nach der Funktion ':: system (...)' verwendet. – iammilind

Verwandte Themen