C++11 Via TFL (Test-Focused Learning): Uniform Initialization

I’ve been working with the new features of C++11 for many months as I write a book on TDD in C++. The updates to the language make for a far-more satisfying experience, particularly in terms of helping me write clean code and tests.

I haven’t written any Java code for over a half-year, and I don’t miss it one bit (I do miss the IDEs a bit, although I’m enjoying working in vim again, particularly with a few new tips picked up from Use Vim Like a Pro and Practical Vim). New language features such as lambdas and type inferencing represent a leap-frogging that shine a little shame on Oracle’s efforts.

source: Naval History and Heritage Command
Image source: Naval History & Heritage Command

Over a series of upcoming blog entries, I will be demonstrating many of the new language features in C++ via test-focused learning (TFL). This entry: uniform initialization, the new scheme for universally-consistent initialization that also simplifies the effort to initialize collections, arrays, and POD types.

One goal of this blog series is to see how well the tests can communicate for themselves. Prepare for a lot of test code (presume they all pass, unless otherwise noted) and little blather. Please feel free to critique by posting comments; there’s always room for improvement around the clarity of tests (particularly regarding naming strategy). Note that TFL and TDD have slightly different goals; accordingly, I’ve relaxed some of the TDD conventions I might otherwise follow (such as one assert per test).

The Basics

Note: Your compiler may not be fully C++11-compliant. The examples shown here were built (and tested) under gcc 4.7.2 under Ubuntu. The unit testing tool is Google Mock (which supports the Hamcrest-like matchers used here, and includes Google Test).

TEST(BraceInitialization, SupportsNumericTypes) {
   int x{42};
   ASSERT_THAT(x, Eq(42));

   double y{12.2};
   ASSERT_THAT(y, DoubleEq(12.2));
}

TEST(BraceInitialization, SupportsStrings) {
   string s{"Jeff"};
   ASSERT_THAT(s, Eq("Jeff"));
}

TEST(BraceInitialization, SupportsCollectionTypes) {
   vector<string> names {"alpha", "beta", "gamma" };
   ASSERT_THAT(names, ElementsAre("alpha", "beta", "gamma"));
}

TEST(BraceInitialization, SupportsArrays) {
   int xs[] {1, 1, 2, 3, 5, 8};
   ASSERT_THAT(xs, ElementsAre(1, 1, 2, 3, 5, 8));
}

Those tests are simple enough. Maps are supported too:

TEST(BraceInitialization, SupportsMaps) {
   map<string,unsigned int> heights {
      {"Jeff", 176}, {"Mark", 185}
   };

   ASSERT_THAT(heights["Jeff"], Eq(176));
   ASSERT_THAT(heights["Mark"], Eq(185));
}

Explicit initialization of collections isn’t nearly as prevalent in production code as it is in tests. I’m tackling uniform initialization first because I’m so much happier with my resulting tests. The ability to create an initialized collection in a single line is far more expressive than the cluttered, old-school way.

TEST(OldSchoolCollectionInitialization, SignificantlyCluttersTests) {
   vector<string> names;

   names.push_back("alpha");
   names.push_back("beta");
   names.push_back("gamma");

   ASSERT_THAT(names, ElementsAre("alpha", "beta", "gamma"));
}

No Redundant Type Specification!

Uniform initialization eliminates the need to redundantly specify type information when you need to pass lists.

TEST(BraceInitialization, CanBeUsedOnConstructionInitializationList) {
   struct ReportCard {
      string grades[5];
      ReportCard() : grades{"A", "B", "C", "D", "F"} {}
   } card;

   ASSERT_THAT(card.grades, ElementsAre("A", "B", "C", "D", "F"));
}
TEST(BraceInitialization, CanBeUsedForReturnValues) {
   struct ReportCard {
      vector<string> gradesForAllClasses() {
         string science{"A"};
         string math{"B"};
         string english{"B"};
         string history{"A"};
         return {science, math, english, history};
      }
   } card;

   ASSERT_THAT(card.gradesForAllClasses(), ElementsAre("A", "B", "B", "A"));
}
TEST(BraceInitialization, CanBeUsedForArguments) {
   struct ReportCard {
      vector<string> subjects_;

      void addSubjects(vector<string> subjects) {
         subjects_ = subjects;
      }
   } card;

   card.addSubjects({"social studies", "art"});

   ASSERT_THAT(card.subjects_, ElementsAre("social studies", "art"));
}

Direct Class Member Initialization

Joyfully (it’s about time), C++ supports directly initializing at the member level:

TEST(BraceInitialization, CanBeUsedToDirectlyInitializeMemberVariables) {
   struct ReportCard {
      string grades[5] {"A", "B", "C", "D", "F"};
   } card;

   ASSERT_THAT(card.grades, ElementsAre("A", "B", "C", "D", "F"));
}

Class member initialization essentially translates to the corresponding mem-init. Be careful if you have both:

TEST(MemInit, OverridesMemberVariableInitialization) {
   struct ReportCard {
      string schoolName{"Trailblazer Elementary"};
      ReportCard() : schoolName{"Chipeta Elementary"} {}
   } card;

   ASSERT_THAT(card.schoolName, Eq("Chipeta Elementary"));
}

Temporary Type Name

TEST(BraceInitialization, EliminatesNeedToSpecifyTempTypeName) {
   struct StudentScore {
      StudentScore(string name, int score)
         : name_(name), score_(score) {}
      string name_;
      int score_;
   };
   struct ReportCard {
      vector<StudentScore> scores_;
      void AddStudentScore(StudentScore score) {
         scores_.push_back(score);
      }
   } card;

   // old school: cardAddStudentScore(StudentScore("Jane", 93));
   card.AddStudentScore({"Jane", 93});

   auto studentScore = card.scores_[0];
   ASSERT_THAT(studentScore.name_, Eq("Jane"));
   ASSERT_THAT(studentScore.score_, Eq(93));
}

Be careful that use of this feature does not diminish readability.

Defaults

TEST(BraceInitialization, WillDefaultUnspecifiedElements) {
   int x{};
   ASSERT_THAT(x, Eq(0));

   double y{};
   ASSERT_THAT(y, Eq(0.0));  

   bool z{};
   ASSERT_THAT(z, Eq(false));

   string s{};
   ASSERT_THAT(s, Eq(""));
}
TEST(BraceInitialization, WillDefaultUnspecifiedArrayElements) {
   int x[3]{};
   ASSERT_THAT(x, ElementsAre(0, 0, 0));

   int y[3]{100, 101};
   ASSERT_THAT(y, ElementsAre(100, 101, 0));
}
TEST(BraceInitialization, UsesDefaultConstructorToDeriveDefaultValue) {
   struct ReportCard {
      string school_;
      ReportCard() : school_("Trailblazer") {}
      ReportCard(string school) : school_(school) {}
   };

   ReportCard card{};

   ASSERT_THAT(card.school_, Eq("Trailblazer"));
}

Odds & Ends

TEST(BraceInitialization, CanIncludeEqualsSign) {
   int i = {99};
   ASSERT_THAT(i, Eq(99));
}

… but why bother?

It’s always nice when a new language feature makes it a little harder to make the dumb mistakes that we all tend to make from time to time (and sometimes, such dumb mistakes are the most devastating).

TEST(BraceInitialization, AvoidsNarrowingConversionProblem) {
   int badPi = 3.1415927;
   ASSERT_THAT(badPi, Eq(3));

   int pi{3.1415927}; // emits warning by default
//   ASSERT_THAT(pi, Eq(3.1415927));
}

Running the AvoidsNarrowingConversionProblem test results in the following warning:

warning: narrowing conversion of ‘3.1415926999999999e+0’ from ‘double’ to ‘int’ inside { } [-Wnarrowing]

Recommendation: use the gcc compiler switch:

-Werror=narrowing

…which will instead cause compilation to fail.

Use With Auto

TEST(BraceInitialization, IsProbablyNotWhatYouWantWhenUsingAuto) {
   auto x{9};
   ASSERT_THAT(x, A<const initializer_list<int>>());
   // in other words, the following assignment passes compilation. Thus x is *not* an int.
   const initializer_list<int> y = x;
}

The Most Vexing Parse?

It’s C++. That means there are always tricky bits to avoid.

TEST(BraceInitialization, AvoidsTheMostVexingParse) {
   struct IsbnService {
      IsbnService() {}
      string address_{"http://example.com"};
   };

   struct Library {
      IsbnService service_;
      Library(IsbnService service) : service_{service} {}
      string Lookup(const string& isbn) { return "book name"; }
   };

   Library library(IsbnService()); // declares a function(!)
//   auto name = library.Lookup("123"); // does not compile

   Library libraryWithBraceInit{IsbnService()};
   auto name = libraryWithBraceInit.Lookup("123");

   ASSERT_THAT(name, Eq("book name"));
}

All the old forms of initialization in C++ will still work. Your best bet, though, is to take advantage of uniform initialization and use it at every opportunity. (I’m still habituating, so you’ll see occasional old-school initialization in my code.)

This entry was posted in C++, TAD, TDD, test-focused learning, TFL, unit testing. Bookmark the permalink.

6 Responses to C++11 Via TFL (Test-Focused Learning): Uniform Initialization

  1. Dale Keener says:

    Jeff,

    Great to hear you’re doing a book on TDD in C++. It’s sorely needed.

    Is there a target date? Who’s publisher?

    Are you providing any early previews for feedback?

    Thanks,

    Dale Keener

  2. jlangr says:

    Hi Dale,

    I’m hoping to see the book out around early summer. The book will be published by PragProg.

    If you’re interested in helping with reviews, please send me an email (jeff@langrsoft.com).

    Regards,
    Jeff

  3. Jeff, these are great! Looking forward to more.

    One minor nit:

    TEST(BraceInitialization, WillDefaultUnspecifiedElements) {
    //....
       double y{};
       ASSERT_THAT(y, Eq(0L));
    // ...
    }

    Shouldn’t that be 0.0 instead of oL? oL is of type long, I believe.

  4. jlangr says:

    Thansk Randy, good spot & problem fixed in the post.

  5. Pingback: C++11: Sum Across a Collection of Objects Using a Lambda or a Range-Based For Loop | langrsoft.com

  6. Pingback: C++11 Via TFL (Test-Focused Learning): Uniform Initialization | langrsoft.com : ???????

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>