Initialization Lists in Constructors
In C++, class members can be initialized not only within the constructor body but also using an initialization list. This syntax appears between the constructor's parameter list and its opening brace:
class Date {
public:
Date(int year, int month, int day)
: year_(year),
month_(month),
day_(day) {}
private:
int year_;
int month_;
int day_;
};
The initialization list is more than just syntactic sugar—it defines where member variables are actually constructed. Key points include:
- All member variables are initialized via the initialization list, even if not explicitly listed.
- Members that are
const, references, or objects of classes without default constructors must be explicitly initialized in the list. - Each member can appear only once in the list.
- Since C++11, in-class member initializers are allowed and serve as default values when not overridden in the list.
- Initialization order follows the declaration order in the class, not the order in the list.
Implicit Type Conversion with Single-Argument Constructors
If a class has a constructor that accepts a single argument (or multiple arguments with defaults), implicit conversion from the argument type(s) to the class type is possible:
class Date {
public:
Date(int year, int month = 1, int day = 1)
: year_(year), month_(month), day_(day) {}
void display() const {
std::cout << year_ << "/" << month_ << "/" << day_ << std::endl;
}
private:
int year_;
int month_;
int day_;
};
int main() {
Date d = 2024; // Implicit conversion: int → Date
d.display();
return 0;
}
To prevent such conversions, mark the constructor as explicit:
explicit Date(int year, int month = 1, int day = 1);
This disables copy-initialization syntax like Date d = 2024;, requiring direct initialization instead.
Static Clas Members
Static Member Variables
A static member variable belongs to the class itself, not any individual object. It is shared across all instances and must be defined out side the class:
class Counter {
public:
Counter() { ++count_; }
static int getCount() { return count_; }
private:
static int count_; // Declaration
};
int Counter::count_ = 0; // Definition and initialization
Static members can be accessed using ClassName::memberName without creating an instance. They respect access control: only public static members are accessible from outside the class.
Static Member Functions
These functions do not operate on specific instances and thus lack a this pointer. As a result, they can only access other static members:
static int getCount() {
return count_; // OK
// return year_; // Error: non-static member
}
They can be called either through the class name or an object, though the latter may mislead readers into thinking instance data is involved.
Friend Functions and Classes
Friend Functions
A friend function is granted access to private and protected members of a class despite not being a member itself:
class Date {
friend void printDate(const Date& d);
public:
Date(int y, int m, int d) : year_(y), month_(m), day_(d) {}
private:
int year_;
int month_;
int day_;
};
void printDate(const Date& d) {
std::cout << d.year_ << "-" << d.month_ << "-" << d.day_ << std::endl;
}
The friend keyword breaks encapsulation selectively, allowing external utilities to inspect internal state.
Friend Classes
A friend class gains full access to another class’s private and protected members:
class Container {
friend class Inspector; // Inspector can access Container's internals
private:
int secretValue = 42;
};
class Inspector {
public:
void reveal(const Container& c) {
std::cout << "Secret: " << c.secretValue << std::endl;
}
};
Note that friendship is neither transitive nor symmetric—declaring A as a friend of B does not imply B is a friend of A, nor does it extend to friends of A.
Inner (Nested) Classes
A class can be defined inside another class. The nested class has no special size relationship with the outer class and is subject to access rules:
class Outer {
private:
int outerData = 100;
class Inner { // Nested class
public:
void expose(const Outer& o) {
std::cout << "Accessing outer: " << o.outerData << std::endl;
}
};
public:
void testInner() {
Inner in;
in.expose(*this);
}
};
An inner class implicitly becomes a friend of its enclosing class, enabling mutual access under proper scoping. However, the reverse is not true unless explicitly declared.
Anonymous Objects
Temporary objects can be created without assigning them to a named variable:
class Logger {
public:
Logger(const std::string& msg) { std::cout << "Log: " << msg << std::endl; }
};
// Usage
Logger("Application started"); // Temporary, destroyed after statement
Such objects are useful for one-time operations like function arguments or immediate method calls. They lifetime ends at the end of the full expression in which they appear.