isSet() or operator void*() or explicit opertor bool() or something else?

↘锁芯ラ 提交于 2019-12-05 04:08:23

I'm impressed that explicit_cast<T> from Imperfect C++: Practical Solutions [...] hasn't been mentioned. The concept is very simple - you set up a pseudo-keyword that actually is a templated class implementing the conversion that you want. I've been using this in my own C++ backports library without any important issue.

class MyClass {
  ...implementation
  operator explicit_cast<bool> () const { 
      (return a bool somehow)
  }
};

You use the pseudo-keyword just as you would expect it to work:

MyClass value;
...
if ( explicit_cast<bool>(myobject) )  {
  do something
} else {
  do something else
}
...

The best part of all, this solution can be mapped perfectly to native explicit operator in C++11, resulting in essentially zero overhead and native syntax. As a result, it's also more generic than trying to figure out if to call "isset", "is_set", "is_Set", "isSet", etc...

PlasmaHH

void* has problems, as it is a valid conversion sequence that was not intended in some cases. Many people use in C++03 sometimes called "safe bool idiom" where you have a local member function pointer type that contains private types so no one could ever have an instance of it outside of your class. You can however return that and at least check for true/false.

When you are using C++11, then explicit operator bool is the way to go, since it was mostly invented for exactly these cases.

Most of the time, one should not use implicit conversion, i.e. using expressions like operator bool() in your code.

When you want to be able to use instances of your class in an if statement, you will often create an implicit conversion, but to a signature of a member function prototype which you will either point to a no-op private function or to NULL dependent on the state.

You will also often overload bool operator!() const for your class. As this will use the same logic as the implicit conversion, you will often implement one in terms of the other.

Something like:

private:
    struct MyPrivateType {};
    void MyPrivateFunc( MyPrivateType ) {}
public:
    typedef void (&iterator::*)( MyPrivateType ) bool_func;

    operator bool_func() const
    {
        return operator!() ? static_cast<bool_func>(0) : MyPrivateFunc;
    }

Nobody can ever call the function you return from the pointer because it requires a MyPrivateType, and they can't get one because it's private.

olibre

Thanks to all your comments/answers/contributions. Here I have merged the different ideas and other ideas found on the net (I have read lots of documentation and source code).


Check the value presence

1. bool isSet() as pointed out by @j_random_hacker

The most logical way. Both source code (library and application) are beginner understandable. And this fits the KISS principle. Moreover this is portable to other programming languages as Java...

Library:               |    Application:
                       |
struct iterator        |
{                      |
  bool isSet() const   |   if (it.isSet())
  {                    |   {
    return p;          |       int v = it.get();
  }                    |       //get() may also call isSet()
                       |       
  int get() const      |       //continue processing
  {                    |   }
     return *p;        |   else //value is not set
  }                    |   {
                       |       //do something else
  int* p;              |   }
};                     |

If the function get() does not check isSet() and a developer (of an application) forgets to call isSet() (before get()) then the application code may crash (segmentation fault).

In the other hand, if get() function calls isSet() therefore isSet() processing is performed twice. Nevertheless recent compilers should avoid such second unnecessary isSet() processing.

2. Return a flag value or default value as proposed by one of my colleagues

Library:               |    Application:
                       |
struct iterator        |   int i = it.get()
{                      |   if (i >= 0)
  int get() const      |   {   
  {                    |     unsigned short v = i;
    if(p) return *p;   |
    else  return -1;   |     //continue processing
  }                    |   }   
                       |   else //value is not set
  unsigned short* p;   |   {
};                     |     //do something else
                       |   }

3. Throwing exception as pointed out by @Matthieu M

Some people think exception is bad for binary code optimization. However, if the throw exception is inlined, best compilers may optimize the binary code, and better than the
Moreover, this solution may allow the best optimized binary code because isSet() is called twice. But this depends on compiler optimization capacities.

Library:

struct iterator
{
  bool get() const  
  { 
     if (isSet()) return *p; 
     else throw; 
  }

private:
  bool isSet() const  { return ....; }      

  ....
};

Application:

int value;
try
{
   value = it.get();
}
catch (...)
{
   value = 0; // default value
}

4. Using operator explicit_cast<bool> () const

Please refer to well written Luis's answer.

5. Using operator to write elegant if(it)

This solution can be well implemented using explicit conversion operators introduced in C++11.

Library:

struct iterator
{
  explicit operator bool() const  { return ....; }

  ....
};

Application:

int value;
if (it)      //very elegant C++ fashion
{
   value = it.get();
}
else
{
   value = 0; // default value
}

However, we are still in 2012, and current source code must be compliant with compilers without support of explicit conversion operators. On theses compilers, different possibilities have been implemented years after years. I present all these ones on the next chapter.


Enable statement if(it) before C++11

The source code of this chapter is inspired from the book More C++ idioms written by Bjarne Stroustrup in 2004, more specifically the section The Safe Bool Idiom as pointed out by @PlasmaHH.

1. implicit operator bool

When explicit is not available, we could just use the implicit conversion operator.

Library:

struct iterator
{
  operator bool() const  { return ....; } //implicit conversion

  ....
};

Application:

int value;
if (it)      //this works very well!
{
   value = it.get();
}
else
{
   value = 0; // default value
}

// But these other instructions are also correct :(
int integer = it;   //convert it to bool, then convert bool to int
if (-6.7 < it)      //.................., then convert bool to double, and compare
  it << 1;

2. operator!

This is the solution used in boost::thread (v1.51) as a workaround of explicit operator bool() for unique_lock, shared_lock, upgrade_lock and upgrade_to_unique_lock.

Library:

struct iterator
{
  bool operator!() const  { return ....; }

  ....
};

Application:

int value;

if (!!it)      // !! looks strange for many developers
{
   value = it.get();
}
else
{
   value = 0; // default value
}

if (it)    //ERROR: could not convert ‘it’ from ‘iterator’ to ‘bool’
{
   value = it.get();
}

3. operator void*

This is the solution used by STL streams. For example refer to file bits/basic_ios.h (std::basic_ios).

Library:

struct iterator
{
  operator void*() const  { return ....; }

  ....
};

Application:

int value;
if (it)      //this works very well!
{
   value = it.get();
}
else
{
   value = 0; // default value
}

// But these other instructions are also correct :(
delete it;  //just a warning: deleting 'void*' is undefined
if (it > std::cin) //both are converted to void*
  void* r = it;

4. implicit conversion to an undefined nested class

This solution has been proposed by Don Box in 1996.

Library:

struct iterator
{
private:
  class nested; //just a forward declaration (no definition)
  int* v_;
public:
  operator nested*() const  { return v_ ? (nested*)this : 0; }
};

Application:

int value;
if (it)      //this works very well!
{
   value = it.get();
}
else
{
   value = 0; // default value
}

// But these other instructions are also correct :(
iterator it2; 
if (it < it2) 
  int i = (it == it2);

5. Safe bool idiom as presented by @CashCow

Bjarne Stroustrup has proposed the utimate solution without drawbacks. Below is a simplified version.

Library:

struct iterator
{
private:
  typedef bool (iterator::*bool_type)() const;
  bool private_() const {}
  int* v_;

public:
  operator bool_type() const  { return v_ ? &iterator::private_ : 0; }
};

//forbids it1 == it2
template <typename T>
bool operator == (const iterator& it,const T& t) { return it.private_(); }

//forbids it1 != it2
template <typename T> 
bool operator != (const iterator& it,const T& t) { return ! (it == t); } 

Application:

int value;
if (it)      //this works very well!
{
   value = it.get();
}
else
{
   value = 0; // default value
}

// All other instructions fail to compile
iterator it2;
if (it >  it2)  ;  //ERROR:  no match for ‘operator>’ in ‘it > it2’
if (it == it2)  ;  //ERROR: ‘bool iterator::private_() const’ is private
if (it != it2)  ;  //same error

6. Reusable safe bool idiom

This is much more complex, please refer to Wikibooks for source code.

Library:

struct iterator : safe_bool <iterator> //I do not want virtual functions
{
   bool boolean_test() const  { return ....; }

   ....
};

Recent STL and boost provide facilities. Some examples:

  • GNU STL --> see files tr1/functional and exception_ptr.h
  • Each Boost component uses its own safe_bool:
    • Spirit --> see files spirit/include/classic_safe_bool.hpp and spirit/home/classic/core/safe_bool.hpp
    • IOstream --> see file iostreams/device/mapped_file.hpp
    • Parameter --> parameter/aux_/maybe.hpp
    • Optional
    • Function
    • Range
    • Logic (tribool)

But Matthew Wilson says in his book Imperfect C++ that safe_bool may lead to size penalties on compilers not implementing Empty Base Optimization. Although most modern compilers do when it comes to single inheritance, there may be a size penalty with multiple inheritance.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!