Czy w przypadku klasy Foo istnieje sposób, aby zabronić jej tworzenia bez nadawania jej nazwy?
Na przykład:
Foo("hi");
I zezwolić na to tylko wtedy, gdy nadasz mu nazwę, taką jak ta?
Foo my_foo("hi");
Czas życia pierwszego to tylko instrukcja, a drugi to otaczający blok. W moim przypadku Foo
jest to pomiar czasu między konstruktorem a destruktorem. Ponieważ nigdy nie odwołuję się do zmiennej lokalnej, często zapominam o jej wstawieniu i przypadkowo zmieniam czas życia. Chciałbym zamiast tego otrzymać błąd czasu kompilacji.
return std::string("Foo");
)Odpowiedzi:
Inne rozwiązanie oparte na makrach:
Oświadczenie
Foo("hi");
rozszerza się doclass Foo("hi");
, które jest źle sformułowane; aleFoo a("hi")
rozszerza sięclass Foo a("hi")
, co jest poprawne.Ma to tę zaletę, że jest zgodny zarówno z kodem źródłowym, jak i binarnym z istniejącym (poprawnym) kodem. (To twierdzenie nie jest całkowicie poprawne - zobacz komentarz Johannesa Schauba i następującą dyskusję poniżej: „Skąd możesz wiedzieć, że jest on zgodny z istniejącym kodem? Jego przyjaciel zawiera swój nagłówek i ma void f () {int Foo = 0;} który wcześniej dobrze się skompilował, a teraz źle się kompiluje! Ponadto każda linia definiująca funkcję składową klasy Foo kończy się niepowodzeniem: void class Foo :: bar () {} " )
źródło
void f() { int Foo = 0; }
który wcześniej dobrze skompilował, a teraz źle się kompiluje! Ponadto, każda linia, która definiuje funkcji składowej klasy Foo nie:void class Foo::bar() {}
.Foo("Hi")
Teraz wewnątrz Foo.cppclass Foo("hi");
jest w porządku do kompilacji.Co powiesz na mały hack
źródło
Foo a("hi");
(bezclass
) też byłby błąd.Foo
ponieważ ma ona pierwszeństwo przed klasą.Foo::Foo("hi")
jest niedozwolone w C ++.Ustaw konstruktor jako prywatny, ale nadaj klasie metodę tworzenia .
źródło
Foo::create();
nadFoo const & x = Foo::create();
std::common_type<Foo>::type()
a otrzymasz tymczasowy. Albo nawettypedef Foo bar; bar()
.std::common_type<Foo>::type()
przez pomyłkę. Pomijanie tegoFoo const & x = ...
przez przypadek jest całkowicie wiarygodne.Ten nie powoduje błędu kompilatora, ale błąd wykonania. Zamiast mierzyć niewłaściwy czas, otrzymujesz wyjątek, który również może być akceptowalny.
Każdy konstruktor, którego chcesz chronić, potrzebuje domyślnego argumentu, który
set(guard)
jest wywoływany.Cechy charakterystyczne to:
Przypadek
f2
,f3
a powrót"hello"
nie może być chciał. Aby zapobiec rzucaniu, możesz pozwolić, aby źródło kopii było tymczasowe, resetując,guard
aby teraz chroniło nas zamiast źródła kopii. Teraz widzisz również, dlaczego użyliśmy powyższych wskaźników - pozwala nam to być elastycznym.Cechy charakterystyczne dla
f2
,f3
ireturn "hello"
są teraz zawsze// OK
.źródło
Foo f = "hello"; // may throw
To wystarczy, żebym przestraszył mnie, żebym nigdy nie używał tego kodu.explicit
i wtedy taki kod już się nie kompiluje. celem było unieważnienie tego, co tymczasowe, i tak jest. jeśli się boisz, możesz sprawić, że nie wyrzuci, ustawiając źródło kopii w konstruktorze kopiowania lub przenoszenia na nienowoczesne. wtedy tylko ostatni obiekt z kilku kopii może zostać rzucony, jeśli nadal kończy się jako tymczasowy.Foo
obiekt jest również tymczasowy, a jego żywotność kończy się tym samym wyrażeniem, co argument domyślny, wówczasFoo
dtor obiektu zostanie wywołany przed dtor argumentu domyślnego, ponieważ pierwszy został utworzony po drugim.Foo(...);
iFoo foo(...);
od wewnątrzFoo
.Kilka lat temu napisałem łatkę dla kompilatora GNU C ++, która dodaje nową opcję ostrzeżenia o takiej sytuacji. Jest to śledzone w elemencie Bugzilli .
Niestety GCC Bugzilla to cmentarzysko, w którym dobrze przemyślane sugestie dotyczące funkcji zawartych w łatkach umierają. :)
Było to motywowane chęcią wyłapania dokładnie tego rodzaju błędów, które są przedmiotem tego pytania, w kodzie, który wykorzystuje lokalne obiekty jako gadżety do blokowania i odblokowywania, mierzenia czasu wykonania i tak dalej.
źródło
W przypadku Twojej implementacji nie możesz tego zrobić, ale możesz użyć tej reguły na swoją korzyść:
Możesz przenieść kod z klasy do wolnostojącej funkcji, która przyjmuje parametr odniesienia inny niż const. Jeśli to zrobisz, otrzymasz błąd kompilatora, jeśli tymczasowy spróbuje powiązać się z odwołaniem innym niż const.
Przykład kodu
Wynik
źródło
x
jest nazwanym obiektem, więc nie jest jasne, czy naprawdę chcemy tego zabronić. Jeśli konstruktor, którego użyłeś, jest jawny, może ludzie zrobiliby to instynktownieFoo f = Foo("hello");
. Myślę, że byliby źli, gdyby to się nie udało. Moje rozwiązanie początkowo odrzuciło to (i bardzo podobne przypadki) z wyjątkiem / niepowodzeniem potwierdzenia i ktoś narzekał.Po prostu nie mają domyślnego konstruktora i wymagają odwołania do wystąpienia w każdym konstruktorze.
źródło
S(selfRef, a);
. : /S(SelfRef, S const& s) { assert(&s == this); }
, jeśli błąd wykonania jest akceptowalny.Nie, obawiam się, że to niemożliwe. Ale możesz uzyskać ten sam efekt, tworząc makro.
Mając to na miejscu, możesz po prostu napisać FOO (x) zamiast Foo my_foo (x).
źródło
Foo();
.class Do_not_use_this_class_directly_Only_use_it_via_the_FOO_macro;
Ponieważ głównym celem jest zapobieganie błędom, rozważ to:
W ten sposób nie możesz zapomnieć o nazwie zmiennej i nie możesz zapomnieć o napisaniu
struct
. Szczegółowy, ale bezpieczny.źródło
Zadeklaruj jednoparametryczny konstruktor jako jawny i nikt nigdy nie utworzy przypadkowo obiektu tej klasy.
Na przykład
można używać tylko w ten sposób
ale nigdy w ten sposób (przez tymczasowe na stosie)
Zobacz też: Alexandrescu, Standardy kodowania C ++, pozycja 40.
źródło
fun(Foo("text"));
.