Професионална програма
Loading...
+ Нов въпрос
MartinBG avatar MartinBG 2738 Точки

Овърлоуд на bool оператора

В скелета на трета задача (TypedStream) намерих следния код:

	// Vector2D.h
    operator std::string() const {
		std::ostringstream s;
		s << "(" << this->x << ", " << this->y << ")";
		return s.str();
	}

Това беше ново за мен и докато го разучавах (пре)открих много други неща, свързани с овърлоудването на оператори в C++. 

Особено интересно в контекста на тази задача ми се стори овърлоудването на bool, което дава възможност за булева проверка на стейта на инстанция от класа:

TestClass test;

if (test) {
   ...
}

В конкретната задача го имплементирах така (private method в TypedStream, защото не го ползвам извън класа):

  explicit operator bool() const {
    return this->stream.good();
  }

Горното работи за задачата, но бърза справка показва, че овърлоудването на bool оператора може и да има нежелани странични ефекти (като кастване през bool до 0/1), решаването на които е свързано с доста по-сложна имплементация:

 

The Safe Bool Idiom

Restrain Conversion Operators with the "Indirect Conversion" Idiom

 

Според тази статия explicit (от C++ 11) би следвало да реши този проблем: 

Since bool is convertible to int and this conversion is not a user defined conversion, enabling an implicit conversion from a type X to bool means, any object of type X can also be implicitly converted to int, giving 0 or 1. Therefore objects of type X could participate in overload resolution in many unexpected cases which can make using X a nightmare. That has been a known problem for a long time, and looking up “safe bool idiom” will give you a lot of information of how not to covert to bool but something that is only convertible to bool. Luckily, C++11 solved the problem by introducing explicit conversion operators and stating that the compiler shall try to explicitly cast objects to bool if they are used in a boolean context, as in `if (x)`.

 

 

Въпросът ми е най-вече към Жоро:

Правилно ли съм разбрал, че explicit решава всички проблеми, произтичащи от овърлоуда на bool оператора, и ако не е така, какво друго трябва да се направи за правилната му имплементация?

Тагове:
2
C++ Programming 28/10/2018 18:53:01
georgi.stef.georgiev avatar georgi.stef.georgiev 921 Точки
Best Answer

Здравей,

Хубаво си обобщил нещата, и, накратко - да, explicit в C++11 решава проблемите, които си описал, и вече е достатъчно разпространен за да се ползва почти навсякъде.

explicit общо взето казва, че за да извикаш конвертиране, трябва да го извикаш изрично. Примерно ако някакъв клас X има explicit operator std::string() { ... }, то тогава този код:

X x;

std::string s = x;

няма да се компилира, заради explicit ключовата дума. За да се компилира, ще трябва да напишем std::string s = (std::string)x. Ако не бяхме написали explicit щеше да се компилира без изричния cast (explicit е "изричен" на български).

Допълнение към това правило обаче е, че ако се ползва в bool контекст - тоест if(x), while(x), и т.н., компилаторът автоматично вмъква (bool), тоест кодът става if((bool)x), while((bool)x) и прочие. Но го вмъква само в такива контексти, не би го вмъкнал в примерно int i = x; - това ще даде компилационна грешка, защото липсва изричното (bool) което се изисква от explicit. Също няма да го вмъкне и в X x1, x2; if (x1 == x2) { ... }, докато ако няма explicit това ще се компилира и ще сравни bool стойностите на x1 и x2, което не искаме, защото bool стойностите на x1 и x2 не казват дали x1 и x2 наистина са "равни".

Така че да, explicit решава всички ситуации, поне за които се сещам. Тези неща предполагам са ти били ясни вече, но реших да ги разпиша в случай, че някой друг чете тази тема.

Това, което не решава обаче, е принципният проблем на operator overloading-а. Много хора ще кажат, че не е много добре такива conversions да се случват - в Java примерно operator overloading изобщо няма и всичко се прави с викане на методи. Викането на метод често е по-четимо и дава повече информация какво проверяваме от това да конвертираме към bool. В C++ обаче има една такава култура на "разбирачество", в която излишни неща се избягват и се очаква, че си достатъчно добър да се справиш дори с по-особен синтаксис. Примерно while(cin) е същото като while(!cin.fail()), обаче това второто почти никъде няма да го видиш, въпреки че за начинаещо е по-четимо.

Така че да, технически погледнато explicit решава проблемите, но не премахва изискването, че overload-и трябва да се правят само тогава, когато е очевидно какво означава bool проверка на един обект. За stream-овете е ясно, за smart pointer-и е ясно, обаче за класът Vehicle най-вероятно не е, и е добре да се избягва.

Поздрави,

Жоро

2