auto
now can deduce type from an expression:
int i = 4; auto j = i;
This is a generalized extension of old C array initializer syntax. {} can define for zero, one, or more elements.
New behavior:
- Prevents any narrow conversions.
- An empty {} uses default initialization (0 for ints).
std::vector<int> numbers = { 1, 2, 3, 4 }; std::vector<int> numbers { 1, 2, 3, 4 };
A constructor can be passed an "initializer list". This snippet shows power and terseness of C++11.
Object( std::initializer_list<int> argList ) { for ( auto arg : argList ) }
Object obj(); // PITFALL! Actually a function declaration! Object obj{};
Another benefit is that {} prevents narrowing conversions (truncations).
for
loops:
General syntax is: for ( type var : list )
std::vector<Object> objects { Object{1}, Object{2}, Object{3} }; for ( Object& object : objects )
Declaring constants in classes isn't intuitive.
Writing const
ought to work but it won't.
Only improvement C++11 offers is ability to declare float constants in classes
(instead of #define kludge) but new syntax is even more arcane.
Which syntax to use depends on if a constant is a simple literal value or a read-only object.
Use static constexpr
(CLASS_CONSTEXPR macro) for constant values of basic types (symbolic constants).
Use static const
(CLASS_CONST macro) for read-only higher-level objects (beyond basic types).
#define CLASS_CONST static const // use for a class object that is const #define CLASS_CONSTEXPR static constexpr // use for a literal constant of a basic type class Class { static constexpr float CONST_FP = 1.2345f; // symbolic constant value of basic type CLASS_CONSTEXPR CONST_FP = 1.2345f; static const std::vector<Class> mReadOnlyData; // read-only high-level object CLASS_CONST std::vector<Class> mReadOnlyData; };
constexpr
:
constexpr tells compiler that an expression must be evalutable at compile-time. A function can be declared constexpr (practically, it should be just a return statement returning constant or constexpr).
nullptr
:
Although null pointers should be eliminated whenever possible. nullptr won't be misinterpreted as a int or long like NULL could.
enum : unsigned int { RR, GG, BB, AA };
New behavior of "enum class":
- Enums within a separate enum scope.
- Every enum is "strongly typed", implicit conversions to int are forbidden.
- Optionally can specify underlying integer type using : type
- Can be forward-declared: enum class ETrafficLight;
- Underlying type is availabe as: std::underlying_type<ETrafficLight>::type
enum class ETrafficLight { RED, YELLOW, GREEN }; enum class ETrafficLight : int { RED, YELLOW, GREEN }; // default underlying type is int enum class ETrafficLight : char { RED, YELLOW, GREEN };
override
:
Suggesting habit of writing override
.
override
tells compiler that a virtual method is supposed to override a base one.
If it doesn't override anything, compiler reports an error.
It catches mistake of a method having same name but a slightly different type,
so that it incorrectly would define a new method instead of overriding an existing one.
Also it can catch when a method was declared virtual but doesn't need to be virtual.
override can be applied to overloaded methods, as long as a matching overload is found.
final
:
final
has two behaviors:
- prevents deriving from a final class.
- prevents overriding a final virtual method.
class FinalDerivative final : public Base // final class virtual void FinalMethod( void ) override final; // final virtual method, should write override and final as a pair
default
:
A C++ rule forbids compiler auto-generating a default constructor if a special constructor was written. C++11 has a new declaration syntax to tell compiler to auto-generate a constructor.
Writing = default
to declare an auto-generated default constructor is recommended if possible,
which avoids mistake of forgetting to copy a particular member.
class Object { Object( const std::string& name ); // special constructor otherwise inhibits auto-generation of other ctors Object( void ) = default; // tell compiler to auto-generate default constructor, otherwise it won't Object( const Object& src ) = default; // tell compiler to auto-generate copy constructor, otherwise it won't Object& operator=( const Object& src ) = default; // tell compiler to auto-generate assignment operator, otherwise it won't };
delete
:
delete
now can be used to disable a method.
This is useful to prevent copying an object that isn't copyable.
Object( const Object& ) = delete; Object& operator=( const Object& ) = delete;
(C++ was originally designed so that constructors weren't inherited. Rationale was that a new constructor is usually necessary.)
Be aware this becomes a PITFALL if derived class has new members.
By writing using
, compiler can be told to bring base constructors into context of derived class:
class Derived : public Base { using Base::Base; // inherit all base constructors };
decltype()
, suffix return type:
decltype()
means type of an expression.
Expression can be a potential expression, an expression that isn't really executed.
suffix return type is a new syntax using combination auto
and ->
with a new meaning:
auto Func() -> returnType;
In practice, this is useful in template functions, using decltype() for return type. In following example, notice that expression (x*y) is an expression that isn't really compiled. decltype() is useful because it returns type of sum (which may involve type promotion), without knowing in advance types. Without this new syntax, would have to resort to hard-coding fpx (extended double) to avoid precision loss.
template<typename FP1,typename FP2> auto Product( const FP1 x, const FP2 y ) -> decltype(x*y) { }
public: Object2( void ) : mObj( [](){ ......... code ...........; return obj; }() ) |<-------------- lamba definition ----------->|^^ function call syntax to call lambda
#define DECLARE_DISTINCT_TYPE( CLASS, T ) \ class CLASS \ { \ public: \ CLASS( T val ) : mVal(val) { } \ operator T() const { return mVal; } \ CLASS& operator=( const T& val ) { mVal = val; return *this; } \ private: \ T mVal; \ }; DECLARE_DISTINCT_TYPE( Radian, float ) DECLARE_DISTINCT_TYPE( Degree, float ) void Rotate( Radian rad ); // these overloaded functions become possible void Rotate( Degree deg ); // yet can be manipulated as floats
// Vector: vector<string> vec; vec.push_back( name ); // push_back() means append vec.clear(); // Iterating thru a container: vector<string> files; vector<string>::iterator itr; for ( itr = files.begin(); itr != files.end(); ++itr ) std::cout << *itr << endl; // Iterating by subscript using an index (N/A to associative containers): vector<MyClass> vec; for ( int i = 0; i < vec.size(); ++i ) // Building and iterating thru a map and using pairs: map<string,int> mp; mp.insert( pair<string,int>( "a", 1 ) ); mp.insert( pair<string,int>( "b", 2 ) ); mp.insert( pair<string,int>( "c", 3 ) ); map<string,int>::iterator itr; for ( itr = mp.begin(); itr != mp.end(); ++itr ) std::cout << itr->first << " " << itr->second << endl; // To test if a key exists in a map/set: if ( mMap.find(key) != mMap.end() ) // Adding then removing from a container. list<int> l; for ( int i = 0; i < 10; ++i ) l.push_back( i ); // append while ( ! l.empty() ) { // Prints/pops oldest (head) element first, std::cout << l.front() << endl; l.pop_front(); } // If no match. if ( map.find("key") == map.end() ) std::cout << "not found" << endl; // Print a container. list<string> con; copy( con.begin(), con.end(), ostream_iterator<string>(std::cout," ") ); // Copy a container: copy( con1.begin(), con1.end(), con2 ); // WRONG/PITFALL if con2 smaller than con1 con2.clear(); copy( con1.begin(), con1.end(), back_inserter(con2) ); // OK if con2 smaller than con1 // Sorting using a binary predicate: // An alternative (using container container pointers instead // of values) that doesn't need a binary predicate is to define // user's own operator<() which sort() uses by default. bool BinPred( const Class& o1, const Class& o2 ) { return o1.val < o2.val; } vector<Class> vec; sort( vec.begin(), vec.end(), BinPred ); // A function that returns a pair. // Pair is returned by value (like a struct would be, so it isn't dangling). std::pair<float,float> AddMul( float x, float y ) { std::pair<float, float> res; res.first = x + y; res.second = x * y; return res; } // Print a binary number in base 2 using bitset. #include <bitset> bitset<8> val = 0xff; std::cout << val; // Turn string to all upper-case. string s; transform( s.begin(), s.end(), s.end(), toupper ); // Distance between two iterators. distance( itr1, itr2 ) // Getting iterator of an item that was inserted into a map: // (a multimap/multiset differs, adapted from gfx_share.hh) std::pair<typename Map::iterator,bool> insertion; insertion = mMap.insert( std::make_pair( *obj, std::make_pair(copy,1) ) ); itr = insertion.first; // insertion is assumed to succeed (bool not checked) // Replacing/substituting chars of a string. // string::replace() is really an overwrite, not a substitution. char ProperDirChar( char c ) { return c == '\' ? '/' : c; } string ProperDir( const string& dirname ) { string s = dirname; std::transform( s.begin(), s.end(), s.begin(), ProperDirChar ); return s; }
// Save/restore stream flags. std::ios::fmtflags savedFlags = std::cout.flags(); ... std::cout.flags(savedFlags); // showbase (eg automatically print "0x" prefix for hex, etc). std::cout.setf( std::ios::showbase ); // Set float precision. mStream.setf( ios::fixed, ios::floatfield ); mStream.precision( 20 ); // Writing to an integer to a stream using width/precision. // Note that width/precision is discarded after every call to stream object! std::cout << std::setw(5) << std::setfill('0') << x << ' '; std::cout << std::setw(5) << std::setfill('0') << y << ' '; std::cout << std::setw(5) << std::setfill('0') << z << ' ' << std::endl; // Convert an int to a string stream for purpose // of passing int as a C++ string. int i; ostringstream ss; ss << i; Print( ss.str() ); // Open for reading to test if file is empty. strm.open( fname, ios::in|ios::binary ); if ( strm.good() && strm.is_open() ) { // File exists. Is it empty? strm.seekg(1); char c; strm >> c; // a read is required to trigger EOF, seekg(1) alone won't if ( strm.eof() ) std::cout << "File exists and is empty." << endl; else std::cout << "File exists and contains data." << endl; } else { std::cout << "File doesn't exist." << endl; } // Reopen in R/W mode. strm.close(); strm.clear(); strm.open( fname, ios::in|ios::out|ios::trunc|ios::binary ); strm.seekp(0);
To write a class that redirects a stream to something else, define user's own streambuf class, then construct an ostream with pointer to streambuf object. An example is in Nicolai Josuttis's STL book.
A faux-pas is trying to derive from std::ostream. One problem is that passing endl will result in a bad cast exception (g++ 3/4).
http://shekel.jct.ac.il/cc-res/online-doc/libgPP/iostream_28.html
http://www.lysator.liu.se/c/bs-errata-1.html
http://gcc.gnu.org/onlinedocs/libstdc++/27_io/howto.html#6
//////////////////////////////////////////////////////////////////////////////// /// @brief NopStream is a indirect way to use a disabled C++ stream. /// /// This is a "streambuf" class to serve as basis of an ostream. /// Derived from Nicolai Josuttis's STL book. /// /// @verbatim /// Example: /// NopStreambuf nopStreamBuf; /// std::ostream gLog( &gNopStreambuf ); /// @endverbatim /// class NopStreambuf : public streambuf { PREVENT_COPYING(NopStreambuf) typedef streambuf Parent; public: NopStreambuf( void ) { } ~NopStreambuf() { } protected: virtual int_type overflow( int_type c ) { return ~EOF; } std::streamsize xsputn( const char* buf, std::streamsize n ) { return n; } };
class Point { public: int x, y; }; ostream& operator<<( ostream& strm, const Point& obj ) { strm << "(" << obj.x << "," << obj.y << ")"; return strm; }
Overloaded operators, except assignment, are inherited. But subtle compiler errors can occur.
Let's say there are two related classes that are logically different types, but are structurally equivalent (identical members).
class Vertex { public: Vertex& operator+( const Vertex& src ); float x, y, z; }; class WorldVertex : public Vertex { public: };
Now try adding two derived objects:
void Draw( const WorldVertex& v ); WorldVertex v1, v2, v3; v3 = v1 + v2; // compile error Draw( v1 + v2 ); // ok
Some compilers will give obscure errors, leading one to think that one needs to duplicate all overloaded operator code into every derived class. That wouldn't be ideal.
What's happening in v3 = v1 + v2
is that Vertex::operator+() is called which returns a base Vertex object,
not a derived WorldVertex object.
One might think assigning a base object into a derived object is an immediate error.
But C++ compiler first tries to find matching assignment operator
such as Derived& operator=( const Base& ):
class WorldVertex : public Vertex { public: WorldVertex& operator=( const Vertex& src ) { x = src.x; y = src.y; z = src.z; return *this; } };
Because these classes are structurally equivalent, Derived& operator=( const Base& ) does make sense, as it can simply copy .x, .y, .z.
This author solved this and all other memory problems by developing two smart-pointers. SharedPtr is a reference-count smart-pointer with reference-count intrusively stored in objects by deriving from Shared base class. (Boost library offers boost::intrusive_ptr.) SafePtr is a smart-pointer template class with private delete operator to prevent deleting.
virtual const
method. Avoid const methods.
class Base { virtual bool IfLoaded( void ) const; }; class Derived { virtual bool IfLoaded( void ); // OOPS! forgot const }; void Func( const Base& obj ) // pitfall opens here { if ( obj.IfLoaded() ) // calls Base::IfLoaded() despite Derived class }
What happens is that Derived::IfLoaded() DOES NOT OVERRIDE base method. Rather, Derived::IfLoaded() BECOMES A DIFFERENT/DISTINCT METHOD!
Reason is that they are methods with different types (const vs. non-const), so C++ treats them as two different/distinct methods, rather than one polymorphic method.
A programmer can too easily forget if a virtual method should be const or non-const. Advice is to avoid any kind const method (except maybe in basic self-contained classes).
This pitfall exists with more complex classes whose members aren't fundamental types. Assignment operators should free members before reassigning them. If an operator=() that frees resources is called in a copy constructor, it will try to free garbage. A solution is have separate Copy() and Free() methods.
class Class { public: Class( const Class& src ) { *this = src; // temptation to write terse code leads to a pitfall } Class& operator=( const Class& src ) { delete mObj; // free members mObj = src.Obj; // reassign members return *this; } private: Class2* mObj; };
Think about order of construction: derived object hasn't been constructed yet.
Safe-guards writing a dummy default copy constructor as private and/or with assert(false).
Write explicit
if conversion isn't desired.
Class( int ); int n; Class obj = n; // converts an int to a Class obj !!
void Byref( int& x ) { ... x = y; } Byref( a + 2 ); // oops, result went nowhere into a temp
operator bool() is seductive for tersely testing if an object is valid:
class Data { public: operator bool() const { return mValid; } private: string mData; }; void Process( Data& data ) { // Valid data? if ( data ) { } }
Let's say two objects are valid but their values (members) differ.
if ( data0 == data1 ) return;
One would think return won't happen. But it will. This is what's compiled:
if ( bool(data0) == bool(data1) ) return; // true == true
Pitfall is implicit conversion. Class doesn't define operator==(). But compiler doesn't supply a default memberwise comparison as might be assumed. Rather, compiler implicity converts both operands to bools. Because that's precisely what operator bool() is for.
Object obj(); // PITFALL! Actually a function declaration! Object obj{};
Another benefit is that {} prevents narrowing conversions (truncations).
for ( itr = files.begin(); itr < files.end(); ++itr ) // WRONG for ( itr = files.begin(); itr != files.end(); ++itr ) // ok
resize() expands container -- reserve() doesn't!
while ( itr++ != files.end() ) // WRONG, itr incremented past end
This could be a bug if vec2 is shorter than vec. copy() won't extend destination container.
copy( vec.begin(), vec.end(), vec2.begin() );
One solution is: vec2.resize( vec1.size() ). Another is to use an insert iterator (insertor):
copy( vec.begin(), vec.end(), back_inserter(vec2) );
char baseName[] = "myfile"; string suffix = "txt"; string fileName = baseName + '.' + suffix; // WRONG
Above is wrong because compiler misinterprets this as C's way of adding an integer to a pointer rather than as C++ string catenation. That is, compiler's intepretation is:
string fileName = &baseName[ int('.') ] + suffix;
if ( mMap[key] == needle )
Above is wrong because map::operator[] will automatically insert a missing key!
if ( mMap.find(key) != mMap.end() && mMap[key] == needle )
A problem is when there are two different types of contains, one directly stores objects, other stores pointers. Then a common template function is needed to process both types, but a problem appears when accessing object via iterator:
typedef std::vector<Object> Objects; typedef std::vector<Object>* ObjectPtrs; Object* obj = &(*iter); // container that stores objects directly Object* obj = *iter; // container that stores pointers to objects
A solution is a wrapper class which gets object as a pointer, allowing template function to be just written in terms of pointers.
//////////////////////////////////////////////////////////////////////////////// /// @brief Wrapper class to get an object from an iterator as a pointer. /// template<class OBJECT_DIRECT, class OBJECT_CONTAINER> class IteratorToPtr { public: typedef OBJECT_DIRECT* Pointer; public: static OBJECT_DIRECT* Get( typename OBJECT_CONTAINER::iterator iter ) { return *iter; } }; //////////////////////////////////////////////////////////////////////////////// /// @brief Wrapper class to get an object from an iterator as a pointer. /// template<class OBJECT_DIRECT, class OBJECT_CONTAINER> class IteratorToPtrByAddress { public: typedef OBJECT_DIRECT* Pointer; public: static OBJECT_DIRECT* Get( typename OBJECT_CONTAINER::iterator iter ) { return &(*iter); } }; /******************************************************************************* * *******************************************************************************/ template<class OBJECT, class OBJECT_CONTAINER, class ITERATOR_TO_PTR> OBJECT* FindObject( int val, OBJECT_CONTAINER& objects ) { for ( typename OBJECT_CONTAINER::iterator iter = objects.begin(); iter != objects.end(); ++iter ) { typename ITERATOR_TO_PTR::Pointer object = ITERATOR_TO_PTR::Get( iter ); if ( object->mVal == val ) return object; } return Object::Dummy(); }
Recommend reading books by Nicolai Josuttis and Scott Meyers.
[2023/05]
Overall, this author still has a positive opinion of C++ and shall continue writing C++.
C++ is the best of the practical/pragmatic programming languages.
This statement does NOT mean C++ is excellent -- it means the alternatives are worse.
C++ became really good with the radical improvements of C++11.
C++ has become way, way, too big. C++23 standard is beyond comprehension. The syntax of C++23 is so complicated, convoluted, incoherent (and broken down, some say) that no programmer can fully trust any compiler to compile every possible syntax combination 100% correctly, hence the stay on the proven subset of C++ rule. (This author does know similar doubts against ALGOL-68 later proved false.) (FORTH compilers were 100% reliable, had ZERO bugs, because of being so tiny.)
"Look at today's situation, people are programming in C++ -- THE WORST DISEASE EVER CREATED! (laughter)
...[C++, Java, C#] They all suffer from their mightiness.
I'm always expecting them to collapse under their own weight."
[Niklaus Wirth, ACM video]
Several mutants of C++ were hyped as the programming language that would replace C++. Fools tried to "correct" C++ by creating imitations of C++ but each was much worse than C++. Even Dr. Frankenstein could not have made a worse abomination than the one Sun Microsystems hacked together. Popularity of each C++ mutant among novices spread, until the novices became experienced and frustrated with their limitations, then each C++ mutant was dumped as trash and forgotten.
D (dlang) merits consideration, nevertheless. D was integrated into GNU gcc-9 in 2019. Code of its reference compiler is readable and written in itself -- D is written in D. But too much expressiveness of C++ was lost by trying to "improve" syntax of C++. Forex, smart-pointers are impossible in D, because D restricts operator-overloading to arithmetic [2023/06].
Because of the complicated syntax of C++23 with all its permutations, no programmer can fully trust any compiler to compile C++23 code 100% correctly. Programmers should avoid the arcane pretzel-logic areas of syntax of C++23, should limit themselves to writing in the proven subset of C++ syntax, never venturing beyond essential improvements of C++11.