8

The following code seems to always follow the true branch.

#include <map>
#include <iostream>

class TestClass {
  // implementation 
}

int main() {
  std::map<int, TestClass*> TestMap;
  if (TestMap[203] == nullptr) {
    std::cout << "true";
  } else {
    std::cout << "false";
  }
  return 0;
}

Is it defined behaviour for an uninitialized pointer to point at nullptr, or an artifact of my compiler?

If not, how can I ensure portability of the following code? Currently, I'm using similar logic to return the correct singleton instance for a log file:

#include <string>
#include <map>    

class Log {
  public:
    static Log* get_instance(std::string path);
  protected:
    Log(std::string path) : path(path), log(path) {};
    std::string path;
    std::ostream log;
  private:
    static std::map<std::string, Log*> instances;
};

std::map<std::string, Log*> Log::instances = std::map<std::string, Log*>();

Log* Log::get_instance(std::string path) {
  if (instances[path] == nullptr) {
    instances[path] = new Log(path);
  }
  return instances[path];
}

One solution would be to use something similar to this where you use a special function provide a default value when checking a map. However, my understanding is that this would cause the complexity of the lookup to be O(n) instead of O(1). This isn't too much of an issue in my scenario (there would only ever be a handful of logs), but a better solution would be somehow to force pointers of type Log* to reference nullptr by default thus making the lookup check O(1) and portable at the same time. Is this possible and if so, how would I do it?

2 Answers 2

10

The map always value-initializes its members (in situations where they are not copy-initialized, of course), and value-initialization for builtin types means zero-initialization, therefore it is indeed defined behaviour. This is especially true for the value part of new keys generated when accessing elements with operator[] which didn't exist before calling that.

Note however that an uninizialized pointer is not necessarily a null pointer; indeed, just reading its value already invokes undefined behaviour (and might case a segmentation fault on certain platforms under certain circumstances). The point is that pointers in maps are not uninitialized. So if you write for example

void foo()
{
  TestClass* p;
  // ...
}

p will not be initialized to nullptr.

Note however that you might want to check for presence instead, to avoid accumulating unnecessary entries. You'd check for presence using the find member function:

map<int, TestClass*>::iterator it = TestMap.find(203);
if (it == map.end())
{
  // there's no such element in the map
}
else
{
  TestClass* p = it->second;
  // ...
}
Sign up to request clarification or add additional context in comments.

9 Comments

Thanks for the quick answer! Just for further clarification, would "TestClass* p();" be initialised to nullptr?
@MrBushido No. TestClass* p(); doesn't declare a variable, it declares a function taking no parameters and returning TestClass*.
MrBushido: int *p; is default-initialized so has indeterminate value. int *p{}; is zero-initialized and 0 converted to a pointer is always the null pointer constant so will compare equal to nullptr.
The answer is using the wrong terminology. The std::map will value-initialize (not default-initailize) a new element in operator[] if it did not exist. It is important to note the difference, as default-initalization of POD types leaves them uninitialized, while value-initialization means zero-initialization for those same types. Replacing value-initialization where default-initialization appears in the answer will make it right.
Trust me, I just checked both standards. In C++03 the description of T() is in 5.2.3p2 The expression T(), where T is a simple-type-specifier (7.1.5.2) for a non-array complete object type or the (possibly cv-qualified) void type, creates an rvalue of the specified type, which is value-initialized *. The same quote in C++11 reads *The expression T(), where T is a simple-type-specifier or typename-specifier for a non-array complete ob- ject type or the (possibly cv-qualified) void type, creates a prvalue of the specified type,which is value- initialized
|
1

Yes, that's defined behaviour. If an element isn't yet in a map when you access it via operator[], it gets default constructed.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.