Advanced Class and Object Concepts in C++
Initialization Lists
Overview
Class constructors can initialize member variables through assignment within the function body, but another method is using initialization lists. An initialization list begins with a colon followed by a comma-separated list of member variables, each followed by an initializing expression in parentheses.
Key Points
- Reference members and const members must be initialized in the list since they cannot be assigned later.
- Custom types without default constructors require explicit initialization via the list.
- C++11 allows default values at member declaration; these are used when not specified in the list.
- Members are initialized in the order of their declaration, not appearance in the list.
- Prefer initialization lists for performance and clarity.
Initialization behavior:
- Members in the list are initialized accordingly.
- Others folow these rules:
- If default value exists: use it.
- Otherwise:
- Built-in types: compiler-dependent, often uninitialized.
- User-defined types: use default constructor if available, otherwise error.
Example Usage
struct Stack {
Stack();
~Stack();
// Some operations may need runtime checks
};
class MyQueue {
public:
MyQueue() : head(nullptr), tail(nullptr) {}
// ...
private:
Stack* head;
Stack* tail;
};
Type Conversion
C++ supports implicit conversion from built-in types to class objects via constructors accepting those types.
Adding explicit before the constructor disables this feature.
Implicit conversion creates a temporary object, which can be optimized away by compilers. Temporary objects are const and should be handled with const references or pointers.
C++11 also supports multi-parameter implicit conversions.
Static Members
Static members belong to the class rather than instances:
- Must be defined outside the class.
- Shared among all instances.
- Stored in static memory area.
- Static functions lack
thispointer. - Can access only static members.
Example implementation counting object creation/destruction:
class Counter {
public:
Counter() { ++count; }
Counter(const Counter&) { ++count; }
~Counter() { --count; }
static int get_count() { return count; }
private:
static int count;
int data = 0;
};
int Counter::count = 0;
Accessing static members:
Counter obj;
int num = Counter::get_count(); // via class scope
int num2 = obj.get_count(); // via instance
Practical Examples
Limit Object Creation
class Singleton {
public:
static Singleton* create_stack_object() {
return new Singleton();
}
static Singleton* create_heap_object(int x, int y) {
Singleton* ptr = new Singleton;
ptr->x = x;
ptr->y = y;
return ptr;
}
private:
Singleton() {}
int x = 1;
int y = 2;
};
Summation Using Static Members
class Accumulator {
public:
Accumulator() {
total += index;
++index;
}
static int get_total() { return total; }
private:
static int index;
static int total;
};
int Accumulator::index = 1;
int Accumulator::total = 0;
class Solution {
public:
int sum_solution(int n) {
Accumulator arr[n];
return Accumulator::get_total();
}
};
Friend Functions and Classes
Friend declarations grant access to private/protected members:
- Friends can be functions or classes.
- Declared inside the class with
friendkeyword. - Not part of the class's interface.
- Can access private members direct.
- Relationships are one-way and non-transitive.
- Reduce encapsulation, so use sparingly.
Nested Classes
A class defined inside another class is called a nested class:
- Independent class, restricted by outer class access control.
- Automatically friend of the outer class.
- Useful when tightly coupled with the outer class.
Example:
class Outer {
public:
class Inner {
public:
Inner(int val = 1) : value(val) {}
void access_outer(const Outer& o) {
std::cout << o.data << std::endl;
}
private:
int value;
};
Outer(int d = 1) : data(d) {}
private:
int data;
};
Anonymous Objects
Anonymous objects are created without naming them:
class Temp {
public:
Temp(int v = 1) : value(v) {}
Temp(const Temp& t) : value(t.value) {}
~Temp() { value = 0; }
void show() const { std::cout << value << std::endl; }
private:
int value;
};
// Anonymous object
Temp(5).show();
Anonymous objects have limited lifetimes and must be const when used as references or pointers.
Compiler Optimization During Copying
Modern compilers optimize copy operations to improve performance:
- Pass-by-value vs pass-by-reference
- Return-by-value vs return-by-reference
- Constructor optimizations (NRVO, RVO)
Note: Chained assignments and overloaded operators may prevent optimization.