C++ Constants
Overview
Constants are data whose values cannot be modified during program execution. C++ provides multiple methods for defining constants, including the const keyword, constexpr keyword, macro definitions, and more. Using constants correctly improves code safety, readability, and performance.
🔒 const Keyword
Basic const Variables
cpp
#include <iostream>
int main() {
// Basic const variables
const int MAX_SIZE = 100;
const double PI = 3.14159;
const char GRADE = 'A';
const std::string MESSAGE = "Hello, World!";
std::cout << "Max size: " << MAX_SIZE << std::endl;
std::cout << "Pi: " << PI << std::endl;
std::cout << "Grade: " << GRADE << std::endl;
std::cout << "Message: " << MESSAGE << std::endl;
// The following code will cause compilation errors
// MAX_SIZE = 200; // Error! Cannot modify const variable
// PI = 3.14; // Error! Cannot modify const variable
// const variables must be initialized at declaration
// const int UNINITIALIZED; // Error! const variable must be initialized
return 0;
}const Pointers
cpp
#include <iostream>
int main() {
int value1 = 10;
int value2 = 20;
// 1. Pointer to const object (the pointed-to value cannot be modified)
const int* ptr_to_const = &value1;
std::cout << "Pointer to const: " << *ptr_to_const << std::endl;
// *ptr_to_const = 30; // Error! Cannot modify value through pointer
ptr_to_const = &value2; // Correct! Can change the pointer itself
std::cout << "After changing pointer: " << *ptr_to_const << std::endl;
// 2. const pointer (the pointer itself cannot be modified)
int* const const_ptr = &value1;
std::cout << "const pointer: " << *const_ptr << std::endl;
*const_ptr = 30; // Correct! Can modify the pointed-to value
// const_ptr = &value2; // Error! Cannot modify the pointer itself
std::cout << "After modifying value: " << *const_ptr << std::endl;
// 3. const pointer to const object
const int* const const_ptr_to_const = &value2;
std::cout << "const pointer to const: " << *const_ptr_to_const << std::endl;
// *const_ptr_to_const = 40; // Error! Cannot modify value
// const_ptr_to_const = &value1; // Error! Cannot modify pointer
return 0;
}const References
cpp
#include <iostream>
#include <string>
// Using const reference as function parameter (avoid copying, prevent modification)
void print_string(const std::string& str) {
std::cout << "String: " << str << std::endl;
// str += " modified"; // Error! Cannot modify const reference
}
// Return const reference
class Person {
private:
std::string name_;
int age_;
public:
Person(const std::string& name, int age) : name_(name), age_(age) {}
// Return const reference, prevent external modification
const std::string& getName() const {
return name_;
}
// Non-const version, allows modification
std::string& getName() {
return name_;
}
int getAge() const { return age_; }
};
int main() {
std::string message = "Hello, C++!";
// const reference can bind to lvalues
const std::string& ref1 = message;
std::cout << "const reference: " << ref1 << std::endl;
// const reference can bind to rvalues
const std::string& ref2 = "Temporary String";
std::cout << "Bind to rvalue: " << ref2 << std::endl;
// Using const reference parameters
print_string(message);
print_string("Direct String");
// const references in class
Person person("Alice", 25);
const Person const_person("Bob", 30);
std::cout << "Regular object name: " << person.getName() << std::endl;
std::cout << "const object name: " << const_person.getName() << std::endl;
// Modify non-const object
person.getName() = "Alice Smith";
std::cout << "After modification name: " << person.getName() << std::endl;
return 0;
}⚡ constexpr Keyword (C++11)
Compile-Time Constants
cpp
#include <iostream>
#include <array>
// constexpr function
constexpr int square(int x) {
return x * x;
}
constexpr int factorial(int n) {
return (n <= 1) ? 1 : n * factorial(n - 1);
}
// constexpr class
class Rectangle {
private:
int width_, height_;
public:
constexpr Rectangle(int w, int h) : width_(w), height_(h) {}
constexpr int area() const {
return width_ * height_;
}
constexpr int perimeter() const {
return 2 * (width_ + height_);
}
};
int main() {
// constexpr variables are computed at compile time
constexpr int SIZE = 10;
constexpr int SQUARED = square(5); // Computed at compile time
constexpr int FACT_5 = factorial(5); // Computed at compile time
std::cout << "SIZE: " << SIZE << std::endl;
std::cout << "Square of 5: " << SQUARED << std::endl;
std::cout << "Factorial of 5: " << FACT_5 << std::endl;
// Can be used for array size (must be compile-time constant)
constexpr int ARRAY_SIZE = square(4);
std::array<int, ARRAY_SIZE> arr;
std::cout << "Array size: " << arr.size() << std::endl;
// constexpr object
constexpr Rectangle rect(3, 4);
constexpr int area = rect.area(); // Computed at compile time
constexpr int perimeter = rect.perimeter(); // Computed at compile time
std::cout << "Rectangle area: " << area << std::endl;
std::cout << "Rectangle perimeter: " << perimeter << std::endl;
// Runtime values cannot be used in constexpr
int runtime_value;
std::cin >> runtime_value;
// constexpr int bad = square(runtime_value); // Error!
int good = square(runtime_value); // Correct, runtime computation
return 0;
}constexpr vs const
cpp
#include <iostream>
int get_runtime_value() {
return 42;
}
int main() {
// const: runtime constant, value cannot be modified after being determined at runtime
const int runtime_const = get_runtime_value();
std::cout << "Runtime constant: " << runtime_const << std::endl;
// constexpr: compile-time constant, value must be determined at compile time
constexpr int compile_time_const = 42;
std::cout << "Compile-time constant: " << compile_time_const << std::endl;
// Error examples
// constexpr int bad = get_runtime_value(); // Error! Not a compile-time constant
// constexpr can be used for template parameters and array sizes
constexpr int TEMPLATE_PARAM = 10;
std::array<int, TEMPLATE_PARAM> compile_time_array;
// const cannot be used where compile-time constant is needed
// std::array<int, runtime_const> runtime_array; // Error!
// constexpr variables are also const
// compile_time_const = 50; // Error! constexpr variable cannot be modified
return 0;
}📍 Macro Definitions (#define)
Basic Macro Definitions
cpp
#include <iostream>
// Simple macro definitions
#define MAX_SIZE 1000
#define PI 3.14159
#define MESSAGE "Hello from macro"
// Function-like macros
#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define ABS(x) ((x) < 0 ? -(x) : (x))
// Multi-line macros
#define PRINT_INFO(name, age) \
do { \
std::cout << "Name: " << (name) << std::endl; \
std::cout << "Age: " << (age) << std::endl; \
} while(0)
// Conditional compilation macros
#define DEBUG_MODE 1
#if DEBUG_MODE
#define DEBUG_PRINT(x) std::cout << "DEBUG: " << (x) << std::endl
#else
#define DEBUG_PRINT(x)
#endif
int main() {
std::cout << "Max size: " << MAX_SIZE << std::endl;
std::cout << "Pi: " << PI << std::endl;
std::cout << "Message: " << MESSAGE << std::endl;
// Using function-like macros
int a = 5, b = 3;
std::cout << "Square of 5: " << SQUARE(a) << std::endl;
std::cout << "Max value: " << MAX(a, b) << std::endl;
std::cout << "Absolute value: " << ABS(-10) << std::endl;
// Using multi-line macros
PRINT_INFO("John", 25);
// Debug macros
DEBUG_PRINT("This is debug information");
return 0;
}Macro Problems and Considerations
cpp
#include <iostream>
#define BAD_SQUARE(x) x * x // Dangerous macro definition
#define GOOD_SQUARE(x) ((x) * (x)) // Safe macro definition
#define INCREMENT(x) ++x // Side effect problem
int main() {
// Problem 1: Operator precedence issue
int result1 = BAD_SQUARE(2 + 3); // Expects 25, actually 11 (2 + 3 * 2 + 3)
int result2 = GOOD_SQUARE(2 + 3); // Correct result 25 ((2 + 3) * (2 + 3))
std::cout << "BAD_SQUARE(2 + 3): " << result1 << std::endl;
std::cout << "GOOD_SQUARE(2 + 3): " << result2 << std::endl;
// Problem 2: Side effect issue
int x = 5;
int result3 = GOOD_SQUARE(INCREMENT(x)); // x is incremented twice!
std::cout << "Value of x: " << x << std::endl; // x = 7, not the expected 6
// Problem 3: Type unsafe
double d = 2.5;
std::cout << "GOOD_SQUARE(2.5): " << GOOD_SQUARE(d) << std::endl;
// Better solution: use constexpr functions
constexpr auto safe_square = [](auto x) { return x * x; };
std::cout << "lambda square: " << safe_square(2 + 3) << std::endl;
return 0;
}🔢 Enumerated Constants
Traditional Enums
cpp
#include <iostream>
// Traditional enum
enum Color {
RED, // 0
GREEN, // 1
BLUE // 2
};
enum Status {
PENDING = 1,
RUNNING = 5,
STOPPED = 10
};
// Anonymous enum used as constant
enum {
BUFFER_SIZE = 1024,
MAX_CONNECTIONS = 100
};
int main() {
Color background = RED;
Status current_status = RUNNING;
std::cout << "Background color: " << background << std::endl;
std::cout << "Current status: " << current_status << std::endl;
// Enums can be implicitly converted to integers
int color_value = background;
std::cout << "Color value: " << color_value << std::endl;
// Using enum constants
char buffer[BUFFER_SIZE];
std::cout << "Buffer size: " << sizeof(buffer) << std::endl;
return 0;
}Strongly-Typed Enums (C++11)
cpp
#include <iostream>
// Strongly-typed enum (enum class)
enum class Direction {
North,
South,
East,
West
};
enum class ErrorCode : int {
Success = 0,
FileNotFound = 1001,
AccessDenied = 1002,
NetworkError = 2001
};
// Specifying underlying type
enum class Priority : char {
Low = 'L',
Medium = 'M',
High = 'H'
};
int main() {
Direction player_direction = Direction::North;
ErrorCode last_error = ErrorCode::Success;
Priority task_priority = Priority::High;
// Strongly-typed enums cannot be implicitly converted to integers
// int dir_value = player_direction; // Error!
int dir_value = static_cast<int>(player_direction); // Requires explicit cast
std::cout << "Direction value: " << dir_value << std::endl;
std::cout << "Error code: " << static_cast<int>(last_error) << std::endl;
std::cout << "Priority: " << static_cast<char>(task_priority) << std::endl;
// Strongly-typed enums avoid name conflicts
enum class Color { Red, Green, Blue };
enum class Traffic { Red, Yellow, Green };
Color car_color = Color::Red;
Traffic light_state = Traffic::Red;
// No conflict, type-safe
return 0;
}🏛️ Static Constant Members
Constants in Classes
cpp
#include <iostream>
#include <string>
class MathConstants {
public:
// static const member (integral types can be initialized in-class)
static const int MAX_ITERATIONS = 1000;
static const char DELIMITER = ',';
// Non-integral static const members need to be defined outside the class
static const double PI;
static const std::string DEFAULT_NAME;
// C++11: static constexpr members
static constexpr double E = 2.71828;
static constexpr int ARRAY_SIZE = 10;
};
// Define static const members outside the class
const double MathConstants::PI = 3.14159265359;
const std::string MathConstants::DEFAULT_NAME = "Unknown";
class Configuration {
private:
const int id_; // Instance const member
const std::string name_; // Instance const member
public:
// const members must be initialized in the initializer list
Configuration(int id, const std::string& name)
: id_(id), name_(name) {}
// const member functions
int getId() const { return id_; }
const std::string& getName() const { return name_; }
// const members cannot be modified
// void setId(int new_id) { id_ = new_id; } // Error!
};
int main() {
// Access static constant members
std::cout << "Max iterations: " << MathConstants::MAX_ITERATIONS << std::endl;
std::cout << "Pi: " << MathConstants::PI << std::endl;
std::cout << "Euler's number: " << MathConstants::E << std::endl;
std::cout << "Default name: " << MathConstants::DEFAULT_NAME << std::endl;
// Using constant array size
int array[MathConstants::ARRAY_SIZE];
std::cout << "Array size: " << sizeof(array) / sizeof(int) << std::endl;
// Instance const members
Configuration config(123, "MyConfig");
std::cout << "Config ID: " << config.getId() << std::endl;
std::cout << "Config name: " << config.getName() << std::endl;
return 0;
}🛡️ const Correctness
const Member Functions
cpp
#include <iostream>
#include <vector>
class Container {
private:
std::vector<int> data_;
mutable int access_count_; // mutable allows modification in const functions
public:
Container() : access_count_(0) {}
// Non-const member function
void add(int value) {
data_.push_back(value);
}
// const member function (promises not to modify object state)
size_t size() const {
access_count_++; // mutable member can be modified
return data_.size();
}
// const version accessor
const int& at(size_t index) const {
if (index >= data_.size()) {
throw std::out_of_range("Index out of range");
}
access_count_++;
return data_[index];
}
// Non-const version accessor
int& at(size_t index) {
if (index >= data_.size()) {
throw std::out_of_range("Index out of range");
}
access_count_++;
return data_[index];
}
// const member functions can call other const member functions
void print() const {
std::cout << "Container size: " << size() << std::endl;
for (size_t i = 0; i < size(); ++i) {
std::cout << at(i) << " ";
}
std::cout << std::endl;
std::cout << "Access count: " << access_count_ << std::endl;
}
};
// Function overloading: const and non-const versions
void process_container(Container& container) {
std::cout << "Processing non-const container" << std::endl;
container.add(42); // Can modify
}
void process_container(const Container& container) {
std::cout << "Processing const container" << std::endl;
// container.add(42); // Error! Cannot call non-const function
container.print(); // Can call const function
}
int main() {
Container container;
container.add(1);
container.add(2);
container.add(3);
// Non-const object
std::cout << "=== Non-const object ===" << std::endl;
process_container(container);
container.print();
// const object
std::cout << "\n=== const object ===" << std::endl;
const Container const_container = container;
process_container(const_container);
return 0;
}Using const_cast
cpp
#include <iostream>
#include <string>
// Legacy API, accepts non-const pointer but doesn't actually modify data
void legacy_function(char* str) {
std::cout << "Legacy function processing: " << str << std::endl;
// Actually doesn't modify str's content
}
class CacheExample {
private:
mutable std::string cached_result_;
mutable bool cache_valid_;
public:
CacheExample() : cache_valid_(false) {}
// const function but needs to modify cache
const std::string& expensive_computation() const {
if (!cache_valid_) {
// Use mutable to avoid const_cast
cached_result_ = "Computed result";
cache_valid_ = true;
}
return cached_result_;
}
// Bad practice: using const_cast
const std::string& bad_computation() const {
if (!cache_valid_) {
// Remove const qualifier (dangerous!)
auto* non_const_this = const_cast<CacheExample*>(this);
non_const_this->cached_result_ = "Computed result";
non_const_this->cache_valid_ = true;
}
return cached_result_;
}
};
int main() {
// Reasonable use of const_cast: calling legacy API
const std::string message = "Hello, World!";
// Need to call legacy function expecting non-const parameter
// legacy_function(message.c_str()); // Error! const char* cannot convert to char*
// Using const_cast (ensure function won't modify data)
legacy_function(const_cast<char*>(message.c_str()));
std::cout << "Message not modified: " << message << std::endl;
// Cache example
const CacheExample cache_obj;
std::cout << "First call: " << cache_obj.expensive_computation() << std::endl;
std::cout << "Second call: " << cache_obj.expensive_computation() << std::endl;
return 0;
}📋 Constant Best Practices
Constant Design Principles
cpp
#include <iostream>
#include <string>
#include <array>
// 1. Use meaningful constant names
namespace GameConfig {
constexpr int MAX_PLAYERS = 4;
constexpr double GRAVITY = 9.81;
constexpr char SEPARATOR = '|';
const std::string DEFAULT_PLAYER_NAME = "Player";
}
// 2. Group related constants together
class NetworkConstants {
public:
static constexpr int DEFAULT_PORT = 8080;
static constexpr int MAX_CONNECTIONS = 100;
static constexpr double TIMEOUT_SECONDS = 30.0;
static const std::string DEFAULT_HOST;
};
const std::string NetworkConstants::DEFAULT_HOST = "localhost";
// 3. Use constexpr instead of macros (when possible)
constexpr double calculate_circle_area(double radius) {
return 3.14159 * radius * radius;
}
// 4. Constant array
constexpr std::array<int, 5> FIBONACCI = {1, 1, 2, 3, 5};
int main() {
// Using namespace constants
std::cout << "Max players: " << GameConfig::MAX_PLAYERS << std::endl;
std::cout << "Gravity constant: " << GameConfig::GRAVITY << std::endl;
// Using class constants
std::cout << "Default port: " << NetworkConstants::DEFAULT_PORT << std::endl;
std::cout << "Default host: " << NetworkConstants::DEFAULT_HOST << std::endl;
// Compile-time computation
constexpr double area = calculate_circle_area(5.0);
std::cout << "Circle area: " << area << std::endl;
// Constant array
std::cout << "Fibonacci sequence: ";
for (const auto& num : FIBONACCI) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}Constant Selection Guide
cpp
#include <iostream>
// Choose the appropriate constant type
int main() {
// 1. Simple values known at compile time: use constexpr
constexpr int COMPILE_TIME_CONSTANT = 42;
// 2. Values determined at runtime but don't change: use const
const int runtime_input = []() {
std::cout << "Enter a number: ";
int value;
std::cin >> value;
return value;
}();
// 3. String literals: usually use const
const std::string APPLICATION_NAME = "MyApp";
// 4. Array sizes and template parameters: must use constexpr
constexpr size_t ARRAY_SIZE = 10;
std::array<int, ARRAY_SIZE> my_array;
// 5. Constants in class: choose static const or static constexpr as needed
class Example {
public:
static const std::string CLASS_NAME; // Runtime constant
static constexpr int CLASS_VERSION = 1; // Compile-time constant
};
std::cout << "Compile-time constant: " << COMPILE_TIME_CONSTANT << std::endl;
std::cout << "Runtime constant: " << runtime_input << std::endl;
std::cout << "Application name: " << APPLICATION_NAME << std::endl;
std::cout << "Array size: " << my_array.size() << std::endl;
return 0;
}Summary
C++ provides multiple methods for defining constants, each with its specific use cases:
Constant Type Comparison
| Type | Features | Use Cases |
|---|---|---|
| const | Runtime constant, immutable | Runtime-determined immutable values |
| constexpr | Compile-time constant, usable in templates | Values known at compile time |
| #define | Preprocessor substitution | Conditional compilation, simple constants |
| enum | Named constant collection | Grouping related constants |
| static const | Class-level constants | Class-related constants |
Best Practices
- Prefer constexpr over #define
- Use const appropriately to ensure data is not accidentally modified
- Organize related constants in namespaces or classes
- Choose meaningful names for constants
- Use const member functions where appropriate
Using constants correctly can:
- Improve code safety
- Enhance code readability
- Enable compiler optimizations
- Facilitate code maintenance