I am using a static method to initialise the const fields of a class. The static method uses some const variables that are stored in a separate header file. Primitive types are correctly being passed to the static method, but the std::strings are being passed empty. I cannot understand why this is.
After doing some searching I have stumbled upon something called the static initialiser fiasco, but I'm having trouble wrapping my head around it, and can't work out if it is to blame. As the the object is in global scope, is the problem that it is being 'setup' before the std::string class has been 'setup'?
I have tried to replicate a minimal example below:
// File: settings.hpp
#include <string>
const std::string TERMINAL_STRING "Printing to the terminal";
const std::string FILE_STRING "Printing to a file";
// File: printer.hpp
#include <string>
#include <iostream>
class Printer
{
private:
const std::string welcomeMessage;
static std::string initWelcomeMessage(std::ostream&);
public:
Printer(std::ostream&);
}
extern Printer::print;
// File: printer.cpp
#include "settings.hpp"
std::string Printer::initWelcomeMessage(std::ostream &outStream)
{
if (&outStream == &std::cout)
{
return (TERMINAL_STRING);
}
else
{
return (FILE_STRING);
}
}
Printer::Printer(std::ostream &outStream) :
message(initWelcomeMessage(outStream)
{
outStream << welcomeMessage << std::endl;
return;
}
// File: main.cpp
#include "printer.hpp"
printer print(std::cout);
int main()
{
return (0);
}
Thanks very much!
As the the object is in global scope, is the problem that it is being 'setup' before the std::string class has been 'setup'?
Yes.
Have your strings be function-statics, returned by reference from some function, instead.
This is the traditional fix for the static initialisation order fiasco.
Related
If I have a class lets say like this in a separate .h file
class myclass{
private:
vector<string> data;
public:
vector <string>& getMydata(){
return this->data;
}
};
How do I then in separate .cpp access the data in the private vector?
the.hpp
#include <string>
#include <vector>
class myclass{
private:
std::vector<std::string> data;
public:
// put some data in it when it's default constructed
myclass() : data{"hello", "world"} {}
std::vector<std::string>& getMydata() {
return data;
}
};
main.cpp
#include "the.hpp"
#include <iostream>
int main()
{
myclass instance;
// get reference to the data in the instance
std::vector<std::string>& data_ref = instance.getMydata();
// use the data. data_ref is a reference to exactly the same vector as in "instance"
data_ref.push_back("Someone from the utside was here!");
// check result
std::cout << instance.getMydata()[0] << "\n";
std::cout << instance.getMydata()[1] << "\n";
std::cout << instance.getMydata()[2] << "\n";
}
Output:
hello
world
Someone from the utside was here!
You separate the implementation into a .cpp the same way you do any function:
vector <string>& myClass::getMydata(){
return this->data;
}
The only difference it you'll need to show that you are implementing the one from the class by putting myClass:: in front of your implementation.
The compiler will allow a member function to access private data, even if it is defined outside the class definition.
i understand that. I guess what i mean to say is in main.cpp how would access the data?
Oh, I see. If you want to access it from main(), or some other non-member function, you need to call your getter:
int main() {
myclass foo;
foo.getMydata().push_back("We're modifying the vector from main()!");
}
I have TestMethods.h
#pragma once
// strings and c-strings
#include <iostream>
#include <cstring>
#include <string>
class TestMethods
{
private:
static int nextNodeID;
// I tried the following line instead ...it says the in-class initializer must be constant ... but this is not a constant...it needs to increment.
//static int nextNodeID = 0;
int nodeID;
std::string fnPFRfile; // Name of location data file for this node.
public:
TestMethods();
~TestMethods();
int currentNodeID();
};
// Initialize the nextNodeID
int TestMethods::nextNodeID = 0;
// I tried this down here ... it says the variable is multiply defined.
I have TestMethods.cpp
#include "stdafx.h"
#include "TestMethods.h"
TestMethods::TestMethods()
{
nodeID = nextNodeID;
++nextNodeID;
}
TestMethods::~TestMethods()
{
}
int TestMethods::currentNodeID()
{
return nextNodeID;
}
I've looked at this example here: Unique id of class instance
It looks almost identical to mine. I tried both the top solutions. Neither works for me. Obviously I'm missing something. Can anyone point out what it is?
You need to move the definition of TestMethods::nextNodeID into the cpp file. If you have it in the header file then every file that includes the header will get it defined in them leading to multiple defenitions.
If you have C++17 support you can use the inline keyword to declare the static variable in the class like
class ExampleClass {
private:
inline static int counter = 0;
public:
ExampleClass() {
++counter;
}
};
Static1.hpp
#include <string>
class Static1
{
public:
static const std::string my_string;
};
Static1.cpp
#include "Static1.hpp"
const std::string Static1::my_string = "aaa";
Static2.hpp
#include <string>
class Static2
{
public:
static const std::string my_string;
};
Static2.cpp
#include "Static2.hpp"
const std::string Static2::my_string = Static1::my_string;
main.cpp
#include "Static2.hpp"
#include <iostream>
int main(argc int, char** argv)
{
cout << to_string(Static2::my_string == "aaa") << endl;
return 0;
}
If I put add_executable(printMyString main.cpp Static2.cpp Static1.cpp) in my CMakeLists.txt, I get
0
while add_executable(printMyString main.cpp Static2.cpp Static1.cpp) gives me the expected behavior of
1
To make my code easier to maintain (so that I don't need to keep track of the order I list my source files), is there any way I can ensure that I get the behavior where Static2::my_string == "aaa"?
You are experiencing effects of a static initialization order fiasco.
The usual work-around is to substitute your static variables with functions that have a static variable in the scope, initialize, and return it.
Here is how it could be done for your example: Live Example (order1)
Live Example (order2)
class Static1
{
public:
static std::string my_string();
};
...
std::string Static1::my_string()
{
static const std::string my_string = "aaa";
return my_string;
}
...
class Static2
{
public:
static std::string my_string();
};
...
std::string Static2::my_string()
{
static const std::string my_string = Static1::my_string();
return my_string;
}
...
std::cout << std::to_string(Static2::my_string() == "aaa") << std::endl;
I'm trying to implement a class (C++) with an enum (with the permitted parameters). I got a working solution, but if I try to extend the functionality I get stuck.
Header data_location.hpp
class DataLocation
{
private:
public:
enum Params { model, period };
std::string getParamString(Params p);
};
Program data_location.cpp
string DataLocation::getParamString(Params p){
static const char * ParamsStrings[] = {"MODEL", "PERIOD"};
return ParamsStrings[p];
}
The array ParamsStrings should be generally available in the class, because I need a second method (with inverse function) returning the enum value given a string.
If I try to define the array in the header I get the error:
in-class initialization of static data member ‘const char* DataLocation::ParamsStrings []’ of incomplete type
Why is the type incomplete? The compiler is for sure able to counts the strings in the array, isn't it?
In case there is no way to get my code working, is there an other way? With 1) no XML; 2) no double definition of the strings; 3) not outside the class; 4) no in code programmed mapping.
In class (header) use keyword static and initialize it outside (.cpp) without the static keyword:
class DataLocation {
public:
enum Params { model, period };
string getParamString(Params p);
static const char* ParamsStrings[];
// ^^^^^^
};
const char* DataLocation::ParamsStrings[] = {"MODEL", "BLLBLA"};
//^^^^^^^^^^^^^^^^^^^^^^^^
The code you have posted is perfectly fine.
Here's the proof:
#include <iostream>
#include <string>
struct DataLocation
{
enum Params { model, period };
std::string getParamString(Params p){
static const char * ParamsStrings[] = {"MODEL", "PERIOD"};
return ParamsStrings[p];
}
};
int main()
{
auto a = DataLocation();
std::cout << a.getParamString(DataLocation::model) << std::endl;
return 0;
}
The error message you are getting is not to do with definition of a static data member in an inline function - that's allowed.
There's something else you're not showing us.
The main issue in my question (the second part) was that if I split the class in .hpp and .cpp the definition of the array (I mixed *char and string) has also to be split:
// data_location.hpp
class DataLocation {
static const char * ParamsStrings[];
}
// data_location.cpp
const char * ParamsStrings[] = {"MODEL", "PERIOD"};
At the end I introduced a consistency check to be sure that the number of values in enum growths as the number of strings. Because the array in C++ is somehow limited I had to go for a std::vector (to get the size).
Code for data_location.hpp
#ifndef DATA_LOCATION_HPP_
#define DATA_LOCATION_HPP_
#include <string>
#include "utils/dictionary.hpp"
extern const char* ENV_DATA_ROOT;
struct EDataLocationInconsistency : std::runtime_error
{
using std::runtime_error::runtime_error;
};
struct EDataLocationNotValidParam : std::runtime_error
{
using std::runtime_error::runtime_error;
};
class DataLocation
{
private:
std::string mRootLocation;
static const std::vector<std::string> msParamsStrings;
static bool msConsistenceCheckDone;
public:
DataLocation();
std::string getRootLocation();
std::string getLocation(Dictionary params);
enum Params { model, period, LAST_PARAM};
std::string Param2String(Params p);
Params String2Param(std::string p);
};
#endif
Code for data_location.cpp
#include "data_location.hpp"
#include <string>
#include <cstdlib>
using namespace std;
const char* ENV_DATA_ROOT = "DATA_ROOT";
bool DataLocation::msConsistenceCheckDone = false;
DataLocation::DataLocation() {
mRootLocation = std::getenv(ENV_DATA_ROOT);
if (not msConsistenceCheckDone) {
msConsistenceCheckDone = true;
if (LAST_PARAM+1 != msParamsStrings.size()) {
throw(EDataLocationInconsistency("DataLocation: Check Params and msParamsStrings"));
}
}
}
string DataLocation::getRootLocation() {
return mRootLocation;
}
string DataLocation::getLocation(Dictionary params) {
// to do
return "";
}
const vector<string> DataLocation::msParamsStrings = { "MODEL", "PERIOD", ""};
string DataLocation::Param2String(Params p) {
if (p>=msParamsStrings.size()) {
throw(EDataLocationNotValidParam("Parameter not found"));
}
return msParamsStrings[p];
}
DataLocation::Params DataLocation::String2Param(string p) {
for (int i = 0; i < msParamsStrings.size(); i++) {
if (p == msParamsStrings[i])
return (Params)i;
}
throw(EDataLocationNotValidParam("Parameter not found"));
}
And also a unit test:
#include <boost/test/unit_test.hpp>
#include "data_location.hpp"
#include <string>
using namespace std;
BOOST_AUTO_TEST_SUITE( data_location )
BOOST_AUTO_TEST_CASE(data_location_1) {
DataLocation dl;
auto s = dl.getRootLocation();
BOOST_CHECK_EQUAL(s, "/home/tc/data/forex" );
BOOST_CHECK_EQUAL(dl.Param2String(DataLocation::period),"PERIOD");
BOOST_CHECK_EQUAL(dl.String2Param("PERIOD"),DataLocation::period);
BOOST_CHECK_THROW(dl.String2Param("SOMETHING"), EDataLocationNotValidParam);
BOOST_CHECK_THROW(dl.Param2String((DataLocation::Params)100), EDataLocationNotValidParam);
}
BOOST_AUTO_TEST_SUITE_END()
C++ is very picky about what it will let you initialize inside of a class definition; there are some particularly non-intuitive rules surrounding static members. It all has to do with the ODR, and why all the rules are the way they are is not especially important.
To cut to the chase, making your array a static constexpr const member should shut the compiler up. With the C++11 standard, the restrictions were relaxed a bit, and one of the new stipulations was that static constexpr members can be initialized inline. This is perfect for your application, since the strings in your array are compile-time constants.
The recent g++ compiler which support C++0x or later compiles thus code. Pure C compile compiles, too. Because strings in initialization like {"MODEL", "PERIOD"}; implemented as const char * pointer to the char array.
Let's say, I've got a following simple code:
Main.cpp
#include "A.h"
// For several reasons this must be a global variable in the project
A a1;
int _tmain(int argc, _TCHAR* argv[])
{
// Another stuff
return 0;
}
A.h
#pragma once
#include <string>
class A
{
private:
// The following works normal if we use simple types like int and etc.
static std::string myString;
public:
A();
};
A.cpp
#include "stdafx.h"
#include "A.h"
// This executes after A::A(), so we are losing all the modifyed content
// If we skip the ="test" part, the string is going to be empty
std::string A::myString = "test";
A::A()
{
// Here myString == ""
myString += "1";
}
The problem is obvious: I cannot use static variables in a constructor of class A in this case as they don't save the changes. Although I need them in order to process some data.
Please, suggest me a solution.
It sounds like you are trying to force the initialization of the static to happen before the constructor is called. The last time I encountered this problem, the only reliable fix was to wrap the static inside a function.
Change the declaration to a function returning reference to string.
static std::string& myString();
Change the definition to a function like this:
std::string& A::myString() {
static std::string dummy = "test";
return dummy;
}
Change your constructor to say:
myString() += "1";
I do not currently have an MSFT compiler handy, so you may have to tweak this a little bit, but this basically forces on-demand initialization of static.
Here is a very short test programming demonstrating how this works:
#include <string>
#include <stdio.h>
std::string& myString() {
static std::string dummy = "test";
return dummy;
}
int main(){
myString() += "1";
printf("%s\n", myString().c_str());
}