Question
How can I create a private static string constant for a C++ class, such as a shape factory, without using a global #define?
For example, I would like code similar to this:
class A {
private:
static const std::string RECTANGLE = "rectangle";
};
However, GCC reports errors because in-class initialization of a static data member of type std::string is not allowed in older C++ standards. What is the standard-compliant way to declare and define a private (or public) string constant owned by a class?
Short Answer
A static const std::string belongs to the class rather than to individual objects. In C++11 through C++14, declare it inside the class and define it once in a .cpp file. In C++17 and later, use an inline static const member and initialize it directly in the class definition.
Concept
A static data member is one value shared by the entire class. It exists independently of any particular object.
A const data member cannot be changed after it is initialized. Combining the two gives a class-owned value that should remain fixed, such as a command name, a file extension, or a configuration key.
std::string is a non-integral object type. In older C++ standards, the class definition may declare a static member, but the storage and initializer must appear in exactly one source file. This rule prevents multiple definitions when a header is included by several .cpp files.
C++17 introduced inline variables. An inline static data member may be defined and initialized in a class definition, even when that definition is placed in a header. The linker treats equivalent inline definitions across translation units as one variable.
Avoid using #define for named constants. Macros have no scope, no type, and are processed before C++ compilation. A class member is scoped, typed, and respects access control.
Mental Model
Think of a class as a building.
- A non-static member is an item in every apartment: every object gets its own copy.
- A static member is a sign in the building lobby: there is one shared copy for every object.
constmeans the sign is permanently fixed after it is installed.
In pre-C++17 C++, the class declaration is the building plan that says the sign exists. The out-of-class definition in one .cpp file is the actual installation of the sign. In C++17, inline static allows the plan to include the installation safely in a header.
Syntax and Examples
For C++11 to C++14, declare the member in the class header and define it once in a source file.
// ShapeFactory.h
#pragma once
#include <string>
class ShapeFactory {
private:
static const std::string RECTANGLE;
public:
static const std::string& rectangleName();
};
// ShapeFactory.cpp
#include "ShapeFactory.h"
const std::string ShapeFactory::RECTANGLE = "rectangle";
const std::string& ShapeFactory::rectangleName() {
return RECTANGLE;
}
Notice the important parts of the definition:
const std::string ShapeFactory::RECTANGLE = "rectangle";
- It is outside the class body.
- It includes the class scope:
ShapeFactory::. - It appears in exactly one
.cppfile. - Do not repeat
staticin this definition.
For C++17 and later, define it directly in the class:
#include <string>
class ShapeFactory {
private:
inline static const std::string RECTANGLE = "rectangle";
};
inline here does not mean the compiler must inline code. It means the variable may have identical definitions in multiple translation units, which makes this header-safe.
Step by Step Execution
Consider this C++11-or-later, pre-C++17 style:
// A.h
class A {
public:
static const std::string RECTANGLE;
};
// A.cpp
const std::string A::RECTANGLE = "rectangle";
// main.cpp
#include "A.h"
#include <iostream>
int main() {
std::cout << A::RECTANGLE << '\n';
}
Execution and build steps:
- The compiler reads
A.h. It learns thatAhas one class-level member namedRECTANGLE. - The compiler reads
A.cpp. The definition allocates and initializes the onestd::stringobject forA::RECTANGLE. - The compiler reads
main.cpp.A::RECTANGLErefers to that declared class member. - The linker connects the use in
main.cppto the single definition inA.cpp. - When the program starts, the string is initialized to
"rectangle"beforemainruns. mainprints:
rectangle
Because the member is static, A::RECTANGLE can be accessed without constructing an A object.
Real World Use Cases
Class-owned string constants are useful when a name is part of a type's API or implementation.
- Factory identifiers:
"rectangle","circle", or"triangle"used to select an object to create. - JSON field names: constants such as
"user_id"and"created_at"prevent repeated string literals. - HTTP headers: a client may centralize names such as
"Authorization"or"Content-Type". - Configuration keys:
"database.host"or"cache.enabled"can be kept near the code that owns the setting. - File formats and extensions: a serializer can own values such as
".json"or"application/json".
Keeping these values in a relevant class gives them a useful scope, for example ShapeFactory::RECTANGLE, rather than exposing a generic global name.
Real Codebase Usage
In production code, choose the representation based on whether you need an owning std::string object.
C++17 class constant
Use this when callers need a std::string or when the class API naturally owns the value.
class ApiRoutes {
public:
inline static const std::string health = "/health";
};
Avoiding repeated literals
A factory can compare input against one canonical name:
std::unique_ptr<Shape> ShapeFactory::create(const std::string& name) {
if (name == RECTANGLE) {
return std::make_unique<Rectangle>();
}
return nullptr;
}
Prefer std::string_view for read-only text views
If the value is a fixed text literal and you only need to compare or read it, C++17 std::string_view avoids owning a dynamically managed string:
#include <string_view>
class ShapeFactory {
public:
inline static constexpr std::string_view rectangle = "rectangle";
};
Use std::string_view carefully: it does not own character storage. A string literal is safe because it exists for the lifetime of the program. A view into a temporary std::string is not safe.
Common Mistakes
Initializing a non-integral static member in a pre-C++17 class definition
This is invalid before C++17:
class A {
static const std::string RECTANGLE = "rectangle"; // Invalid before C++17
};
Fix it by declaring it in the class and defining it in one .cpp file, or compile as C++17 and add inline static.
Forgetting the out-of-class definition
This declaration alone is not enough if the value is used:
class A {
static const std::string RECTANGLE;
};
Add one definition:
const std::string A::RECTANGLE = "rectangle";
Otherwise, the linker commonly reports an undefined reference.
Adding static again outside the class
Do not write this:
static const std::string A::RECTANGLE = "rectangle"; // Wrong
The member was declared static inside the class. The definition uses the member's existing linkage and only needs its type and qualified name.
Defining the member in a header before C++17
This causes multiple-definition linker errors when several .cpp files include the header:
// Header: do not do this before C++17
const std::string A::RECTANGLE = "rectangle";
Put the definition in one .cpp file. In C++17, use inline static in the class instead.
Returning a mutable copy unnecessarily
If an accessor is needed, returning const std::string& avoids copying:
static const std::string& rectangleName();
Return by value only when the caller should receive an independent string.
Using a macro for a scoped constant
#define RECTANGLE "rectangle"
This has no class scope and can conflict with another macro or identifier. Prefer a class member or a namespace-level constant.
Comparisons
| Option | Scope | Owns characters? | Best use |
|---|---|---|---|
static const std::string | Class | Yes | A class-owned string object, especially when a std::string API is required |
inline static const std::string (C++17+) | Class | Yes | Header-only or modern class definitions |
inline static constexpr std::string_view (C++17+) | Class | No | Fixed literals used for reading and comparison |
constexpr const char* | Class or namespace | No | Interoperating with C APIs; less expressive than string_view |
#define macro | No C++ scope | No | Generally avoid for constants |
Namespace inline constexpr std::string_view | Namespace | No | A shared constant not owned by one class |
std::string owns its data and is convenient when an API requires a string object. std::string_view is lightweight but only borrows a sequence of characters, so its source data must outlive the view.
Cheat Sheet
// C++11 through C++14: header
class A {
private:
static const std::string rectangle;
};
// C++11 through C++14: exactly one .cpp file
const std::string A::rectangle = "rectangle";
// C++17+: header-safe class member
class A {
private:
inline static const std::string rectangle = "rectangle";
};
// C++17+: efficient fixed literal
#include <string_view>
class A {
private:
inline static constexpr std::string_view rectangle = "rectangle";
};
Rules:
- Include
<string>forstd::stringand<string_view>forstd::string_view. - Use
std::string, unless you intentionally useusing std::string;. - Before C++17, define a non-inline static
std::stringin one.cppfile. - In an out-of-class member definition, write
A::memberNameand do not repeatstatic. constprevents modification through that member name;privateprevents outside code from naming it.
FAQ
Can a static std::string be initialized inside a class?
Yes in C++17 or later with inline static const std::string. Before C++17, declare it in the class and define it in one .cpp file.
Why does static const int sometimes work inside a class without a .cpp definition?
Older C++ rules allow in-class initializers for certain integral or enumeration constants when initialized with constant expressions. std::string is an object type, so it does not follow that special rule.
Should I use constexpr std::string?
Not for a portable C++17 solution. std::string was not generally usable as a compile-time constant in the way string literals are. For a fixed literal, constexpr std::string_view is often the better choice.
Is static constexpr std::string_view private by default in a class?
Yes. Members before a public: label in a class are private by default.
Does const make the std::string compile-time constant?
No. const means it cannot be modified after initialization. It does not necessarily mean compile-time evaluation.
Can outside code read a private static constant?
No, not directly. Provide a public function, such as rectangleName(), if outside code needs access.
Do I need an object to access a static member?
No. Use the class name: ShapeFactory::rectangleName() or, when public, ShapeFactory::RECTANGLE.
Mini Project
Description
Build a small shape factory that accepts a text name and creates either a rectangle or a circle. The factory keeps its accepted names as private, class-owned constants so spelling is centralized and macros are unnecessary.
Goal
Create a factory with private static string constants and a public method that maps a shape name to a result.
Requirements
- Create a
ShapeFactoryclass with private names for"rectangle"and"circle". - Use C++17 inline static constants.
- Add a public
createfunction that accepts aconst std::string&. - Return a descriptive result for known shape names.
- Return an error message for an unknown name.
Keep learning
Related questions
Advantages of Brace Initialization in C++
Learn why C++ brace initialization is often clearer and safer than other object initialization styles, with examples and common pitfalls.
Basic Rules and Idioms for Operator Overloading in C++
Learn the core rules, syntax, and common idioms for operator overloading in C++, including member vs non-member operators.
C++ Aggregates, Trivial Types, Trivially Copyable Types, and PODs Explained
Learn what aggregates, trivial types, trivially copyable types, and PODs mean in C++, how they differ, and why they matter.