C++ Classes
Class Definition
class Host {
public:
// Constructor
Host(std::string name, std::string ip, int port)
: name_(std::move(name))
, ip_(std::move(ip))
, port_(port)
, active_(true) {}
// Destructor
~Host() = default;
// Methods
std::string address() const {
return ip_ + ":" + std::to_string(port_);
}
void deactivate() { active_ = false; }
bool is_active() const { return active_; }
private:
std::string name_;
std::string ip_;
int port_;
bool active_;
};
Host h("sw01", "10.50.1.10", 22);
std::cout << h.address() << std::endl;
Member initializer lists (: name_(name)) are more efficient than assignment in the constructor body. const methods promise not to modify the object.
Access Control
class Device {
public: // accessible to anyone
std::string name() const { return name_; }
protected: // accessible to derived classes
void log(const std::string& msg) {
std::cout << "[" << name_ << "] " << msg << std::endl;
}
private: // accessible only within this class
std::string name_;
std::string ip_;
};
struct defaults to public access. class defaults to private access. Use struct for plain data, class when you have invariants to protect.
Inheritance
class NetworkDevice : public Device {
public:
NetworkDevice(std::string name, int vlan)
: Device(std::move(name)), vlan_(vlan) {}
virtual std::string describe() const {
return name() + " (VLAN " + std::to_string(vlan_) + ")";
}
virtual ~NetworkDevice() = default; // always virtual dtor in base
private:
int vlan_;
};
class Switch : public NetworkDevice {
public:
using NetworkDevice::NetworkDevice;
std::string describe() const override {
return "Switch: " + NetworkDevice::describe();
}
};
virtual enables runtime polymorphism. override is a compile-time check that you are actually overriding a virtual method. Always make destructors virtual in base classes.
Rule of Five
class Buffer {
public:
Buffer(size_t size) : data_(new char[size]), size_(size) {}
~Buffer() { delete[] data_; } // Destructor
Buffer(const Buffer& other) // Copy constructor
: data_(new char[other.size_]), size_(other.size_) {
std::copy(other.data_, other.data_ + size_, data_);
}
Buffer& operator=(const Buffer& other) { // Copy assignment
if (this != &other) {
delete[] data_;
size_ = other.size_;
data_ = new char[size_];
std::copy(other.data_, other.data_ + size_, data_);
}
return *this;
}
Buffer(Buffer&& other) noexcept // Move constructor
: data_(other.data_), size_(other.size_) {
other.data_ = nullptr;
other.size_ = 0;
}
Buffer& operator=(Buffer&& other) noexcept { // Move assignment
if (this != &other) {
delete[] data_;
data_ = other.data_;
size_ = other.size_;
other.data_ = nullptr;
other.size_ = 0;
}
return *this;
}
private:
char* data_;
size_t size_;
};
If your class manages a resource (memory, file handle, socket), you need all five special members. If it uses only RAII wrappers (smart pointers, containers), use = default for all five.
Operator Overloading
class Point {
public:
double x, y;
Point operator+(const Point& other) const {
return {x + other.x, y + other.y};
}
bool operator==(const Point& other) const {
return x == other.x && y == other.y;
}
friend std::ostream& operator<<(std::ostream& os, const Point& p) {
os << "(" << p.x << ", " << p.y << ")";
return os;
}
};
Point a{1.0, 2.0}, b{3.0, 4.0};
Point c = a + b;
std::cout << c; // (4, 6)
friend grants the non-member function access to private members. The stream insertion operator << must be a non-member function.