Reputation: 3
while I was doing hackerrank c++ exercises I stumbled upon this code in the discussions section:
class BadLengthException : public std::runtime_error
{
public:
BadLengthException(int length) : std::runtime_error{std::to_string(length)}
{ }
};
I don't really understand what is going on after the member initializer part, this part to be exact:
std::runtime_error{std::to_string(length)}
Can someone explain what this line of code does to me? I have never seen such a use of member initialization. I am used to seeing:
Foo(int num) : bar(num) {};
So please explain it as clearly as possible. Thank you for your time!
Upvotes: 0
Views: 90
Reputation: 1409
You are inheriting from the standard exception class std::runtime_error
.
In this code:
class BadLengthException : public std::runtime_error
{
public:
BadLengthException(int length) std::runtime_error{std::to_string(length)}
{ }
};
You are defining a new exception class in terms of std::runtime_error
.
std::runtime_error
takes a string message as input, which you can print with runtime_error_object.what()
in a catch
block. So, that is why the length
variable is being converted to a std::string
. You can read more about that here.
Lastly:
Foo(int num) : bar(num) {};
This is constructor list initializer syntax. That is used to initialize member variables of a class. You can read more about that here.
Upvotes: 1
Reputation: 98425
First, note that the base class has the same member syntax as any other member in the initialization list. Say, you got a base class:
class Base {
int m_val;
public:
explicit Base(int val) : m_val(val) {};
int val() const { return m_val; }
};
Now, you'd want to initialize m_val
in the derived class - you can't, since it's private to Base
. But you can initialize it by explicitly initializing the base class instance that the derived class "wraps around":
class Derived {
int m_myVal;
public:
Derived() : Base(42), m_myVal(12) {
assert(val() == 42 && m_myVal == 12);
}
};
In C++11, you can use new syntax for the initializations. It's called the uniform initialization syntax. The above can be rewritten as follows:
class Base {
int m_val;
public:
explicit Base(int val) : m_val{val} {};
int val() const { return m_val; }
};
// Approach 1
class Derived {
int m_dval;
public:
Derived() : Base{42}, m_myVal{12} {
assert(val() == 42 && m_myVal == 12);
}
};
// Approach 2
class Derived {
int m_dval = 12;
public:
Derived() : Base{42} {
assert(val() == 42 && m_myVal == 12);
}
};
Thus, your code in context:
namespace my {
// We make our own classes, equivalent to those in std::, for purpose of exposition:
class exception {
public:
exception() noexcept = default;
virtual ~exception() = default;
virtual const char* what() const noexcept { return ""; }
};
class runtime_error {
std::string m_what;
public:
explicit runtime_error(const std::string &what) : m_what(what) {}
const char* what() const noexcept override { return m_what.c_str(); }
};
}
class BadLengthException : public my::runtime_error {
public:
// pre-C++11 syntax
BadLengthException(int length) : my::runtime_error(std::to_string(length)) {
assert(what() == std::to_string(length));
}
// or, with C++11 syntax
BadLengthException(int length) : my::runtime_error{std::to_string(length)} {
assert(what() == std::to_string(length));
}
};
There, my::runtime_error
is written in reference to the base class, in order to pass arguments to its constructor.
In the above code, assert
s aren't merely runtime checks for debug builds, they also document true statements about the code. There's an unambiguous way of reading them in English - a good skill to have.
There's a natural tendency to write comments about expected state of the program:
// Don't do this
Derived::Derived() : Base{42} {
// Here val() is 42 and m_myVal is 12
}
That's not idiomatic C++: comments about the state would be helpful, but never when code can be used to not only formally pose the true statement (the assertion), but also have it verified at runtime. Moreover, assertions can be leveraged by code optimizers and by static analysis tools. As one gains enlightenment, the following is often seen:
// Don't do this: don't repeat yourself (DRY)
Derived::Derived() : Base{42} {
assert(val() == 42 && m_myVal == 12);
// Here val() is 42 and m_myVal is 12
}
The idiomatic way is with just an assert - no need for comments, it's plenty understandable as-is.
Derived::Derived() : Base{42} {
assert(val() == 42 && m_myVal == 12);
}
Upvotes: 0