In my understanding it is not possible to store the address of a local memory in a global container, because the local variable will eventually be destroyed.
class AA {
std::string name;
public:
explicit AA(std::string n) : name(n) {}
std::string getName() const {
return name;
}
};
class Y {
std::vector<std::reference_wrapper<AA>> x;
public:
void addAA(AA& aa) {
x.emplace_back(aa);
}
AA& getLastA() {
return x.back();
}
};
void foobar(Y& y) {
AA aa("aa");
y.addAA(aa);
}
int main() {
Y y;
foobar(y);
std::cout << y.getLastA().getName() << std::endl; // error - probably because the local aa has been destroyed.
return 0;
}
However, I do not understand why this code works:
void foobar(Y& y, AA& aa) {
y.addAA(aa);
}
int main() {
Y y;
{
AA aa("aa");
foobar(y,aa);
}
std::cout << y.getLastA().getName() << std::endl;
return 0;
}
aa is again created in another scope and should be destroyed. However, it is possible to get its name later in main. The code works fine.
Why don't we get an error in the second case?
In my understanding it is not possible to store the address of a local memory in a global container, because the local variable will eventually be destroyed.
You're starting from a flawed understanding. It's certainly a bad idea to keep the address of a local variable beyond its lifetime, but it's still possible. As you say, it's a bad idea because that pointer will quickly become invalid. Even so, you can take the address of any variable, and the language won't prevent you from storing it wherever you like. Like C before it, C++ lets you do a great many things that are bad ideas, so be careful.
Rust has a borrow checker that permeates the language and is its defining characteristic. In Rust, such code would be forbidden.
C++ is a language with a long history and the reason why there's no error diagnostics for this, I assume, is because when the language was created it was not a concern. At those times the C philosophy was mainstream: "the programmer knows everything". So, it would follow that the programmer knows that they violated lifetime safety, and therefore the error message is redundant.
Now, the C++ committee is preoccupied with making sure that the versions of C++ are as much backwards-compatible as possible, and enforcing the provided snippet to fail may break existing code. Thus, it is not in the near future for lifetime violations to be in the standard any time soon.
What committee seems to slide towards regarding this kind of question is that if you want to enforce something that's not covered by the standard, you should use third-party static analysis tools. There are few of them, though, most notable is clang-tidy, but none of them, as far as I know, support rigorous lifetime analysis to detect the errors demonstrated by your snippet.
Related
For a strictly internal class that is not intended to be used as part of an API provided to an external client, is there anything inherently evil with initializing a class pointer member variable to itself rather than NULL or nullptr?
Please see the below code for an example.
#include <iostream>
class Foo
{
public:
Foo() :
m_link(this)
{
}
Foo* getLink()
{
return m_link;
}
void setLink(Foo& rhs)
{
m_link = &rhs;
// Do other things too.
// Obviously, the name shouldn't be setLink() if the real code is doing multiple things,
// but this is a code sample.
}
void changeState()
{
// This is a code sample, but play along and assume there are actual states to change.
std::cout << "Changing a state." << std::endl;
}
private:
Foo* m_link;
};
void doSomething(Foo& foo)
{
Foo* link = foo.getLink();
if (link == &foo)
{
std::cout << "A is not linked to anything." << std::endl;
}
else
{
std::cout << "A is linked to something else. Need to change the state on the link." << std::endl;
link->changeState();
}
}
int main(int argc, char** argv)
{
Foo a;
doSomething(a);
std::cout << "-------------------" << std::endl;
// This is a mere code sample.
// In the real code, I'm fetching B from a container.
Foo b;
a.setLink(b);
doSomething(a);
return 0;
}
Output
A is not linked to anything.
-------------------
A is linked to something else. Need to change the state on the link.
Changing a state.
Pros
The benefit to initializing the pointer variable, Foo::link, to itself is to avoid accidental NULL dereferences. Since the pointer can never be NULL, then at worst, the program will produce erroneous output rather than segmentation fault.
Cons
However, the clear downside to this strategy is that it appears to be unconventional. Most programmers are used to checking for NULL, and thus don't expect to check for equality with the object invoking the pointer. As such, this technique would be ill-advised to use in a codebase that is targeted for external consumers, that is, developers expecting to use this codebase as a library.
Final Remarks
Any thoughts from anyone else? Has anyone else said anything substantial on this subject, especially with C++98 in consideration? Note that I compiled this code with a GCC compiler with these flags: -std=c++98 -Wall and did not notice any issues.
P.S. Please feel free to edit this post to improve any terminology I used here.
Edits
This question is asked in the spirit of other good practice questions, such as this question about deleting references.
A more extensive code example has been provided to clear up confusion. To be specific, the sample is now 63 lines which is an increase from the initial 30 lines. Thus, the variable names have been changed and therefore comments referencing Foo:p should apply to Foo:link.
It's a bad idea to start with, but a horrendous idea as a solution to null dereferences.
You don't hide null dereferences. Ever. Null dereferences are bugs, not errors. When bugs happens, all invariances in your program goes down the toilet and there can be no guarantee for any behaviour. Not allowing a bug to manifest itself immediately doesn't make the program correct in any sense, it only serves to obfuscate and make debugging significantly more difficult.
That aside, a structure pointing into itself is a gnarly can of worms. Consider your copy assignment
Foo& operator=(const Foo& rhs) {
if(this != &rhs)
return *this;
if(rhs->m_link != &rhs)
m_link = this;
else
m_link = rhs->m_link;
}
You now have to check whether you're pointing to yourself every time you copy because its value is possibly tied to its own identity.
As it turns out, there's plenty of cases where such checks are required. How is swap supposed to be implemented?
void swap(Foo& x, Foo& y) noexcept {
Foo* tx, *ty;
if(x.m_link == &x)
tx = &y;
else
tx = x.m_link;
if(y.m_link == &y)
ty = &x;
else
ty = y.m_link;
x.m_link = ty;
y.m_link = tx;
}
Suppose Foo has some sort of pointer/reference semantics, then your equality is now also non-trivial
bool operator==(const Foo& rhs) const {
return m_link == rhs.m_link || (m_link == this && rhs.m_link == &rhs);
}
Don't point into yourself. Just don't.
Foo is responsible for its own state. Especially pointers it exposes to its users.
If you expose a pointer in this fashion, as a public member, it is a very odd design decision. My gut has told me the last 30 odd years a pointer like this is not a responsible way to handle Foo's state.
Consider providing getters for this pointer instead.
Foo* getP() {
// create a safe pointer for user
// and indicate an error state. (exceptions might be an alternative)
}
Unless you share more context what Foo is, advice is hard to provide.
is there anything inherently evil with initializing a class pointer member variable to itself rather than NULL or nullptr?
No. But as you pointed out, there might be different considerations depending on the use case.
I'm not sure this would be relevant under most circumstances, but there are some instances where an object needs to hold a pointer of its own type, so its really just pertinent to those cases.
For instance, an element in a singly-linked list will have a pointer to the next element, so the last element in the list would normally have a NULL pointer to show there are no further elements. So using this example, the end element could instead point to itself instead of NULL to denote it is the last element. It really just depends on personal implementation preference.
Many times, you can end up obfuscating code needlessly when trying too hard to make it crash-proof. Depending on the situation, you might mask issues and make problems much harder to debug. For instance, going back to the singly-linked example, if the pointer-to-self initialization method is used, and a bug in the program attempts to access the next element from the end element in the list, the list will return the end element again. This would most likely cause the program to continue "traversing" the list for eternity. That might be harder to find/understand than simply letting the program crash and finding the culprit via debugging tools.
I ran into a nasty bug in some of my code. Here's the simplified version:
#include <iostream>
class A
{
public:
std::string s;
void run(const std::string& x)
{
// do some "read-only" stuff with "x"
std::cout << "x = " << x << std::endl;
// Since I passed X as a const referece, I expected string never to change
// but actually, it does get changed by clear() function
clear();
// trying to do something else with "x",
// but now it has a different value although I declared it as
// "const". This killed the code logic.
std::cout << "x = " << x << std::endl;
// is there some way to detect possible change of X here during compile-time?
}
void clear()
{
// in my actual code, this doesn't even happen here, but 3 levels deep on some other code that gets called
s.clear();
}
};
int main()
{
A a;
a.s = "test";
a.run(a.s);
return 0;
}
Basically, the code that calls a.run() use to be used for all kinds of strings in the past and at one point, I needed the exact value that object "a.s" had, so I just put a.s in there and then some time later noticed program behaving weird. I tracked it down to this.
Now, I understand why this is happening, but it looks like one of those really hard to trace and detect bugs. You see the parameter declared as const & and suddenly it's value changes.
Is there some way to detect this during compile-time? I'm using CLang and MSVC.
Thanks.
Is there some way to detect this during compile-time?
I don't think so. There is nothing inherently wrong about modifying a member variable that is referred by a const reference, so there is no reason for the compiler to warn about it. The compiler cannot read your mind to find out what your expectations are.
There are some usages where such wrong assumption could result in definite bugs such as undefined behaviour that could be diagnosed if identified. I suspect that identifying such cases in general would be quite expensive computationally, so I wouldn't rely on it.
Redesigning the interface could make that situation impossible For example following:
struct wrapper {
std::string str;
};
void run(const wrapper& x);
x.str will not alias the member because the member is not inside a wrapper.
This question already has answers here:
Can I access private members from outside the class without using friends?
(27 answers)
Closed 2 years ago.
I have a homework in which:
we have this code:
#include <iostream>
using namespace std;
class Test {
int x;
char y;
public:
Test() :x(0), y(0) { ; }
};
int main() {
Test t;
//Do stuff!
return 0;
}
and without adding getters and setters or using friend class we have to read x and y and also change them.
I searched and found these ways:
if there was a template function in my class I could say:
class Test {
int x;
char y;
public:
Test() :x(0), y(0) { ; }
template<typename T>
void do_something() {//not necessarily void function
//Do some stuff
};
};
class a;
// My specialization.
template <>
void Test::do_something<a>() {
cout << x << endl;
cout << y << endl;
// getting data
x = 5;
y = 'a';
// changing data
cout << x << endl;
cout << y << endl;
// getting data after changes we made
}
int main() {
Test t;
t.do_something<a>();
return 0;
}
and also the method, which I think is this question answer, is using pointers.
like this:
class Test {
int x;
char y;
public:
Test() :x(0), y('0') { ; }
};
int main() {
Test t;
int* ptr = (int*)&t;
cout << "x = " << *ptr << " y = " << (char)*(ptr + 1) << endl;
*ptr--;
//getting data
*ptr = 12;
ptr++;
*ptr = 65;
//changing data
ptr--;
cout << "x = " << *ptr << " y = " << (char)*(ptr + 1) << endl;
//getting data after changes we have made
return 0;
}
or using reinterpret_cast and pointers:
struct pointer {
int x;
char y;
};
class Test {
int x;
char y;
public:
Test() :x(0), y('0') { ; }
};
int main()
{
Test t;
pointer* p = reinterpret_cast<pointer*>(&t);
cout << "X = " << p->x << " Y = " << p->y << endl;
//getting data
p->x = 5;
p->y = 'a';
//changing data
cout << "X = " << p->x << " Y = " << p->y << endl;
//getting data from class after changing them with pointers
return 0;
}
my questions are:
is such thing possible in other object oriented languages?
does this mean access modifiers are useless?
and is there anything we can do to prevent such thing to happen?
(with pointers) why this happen?
I don't understand this one, so I will skip it.
is such thing possible in other object oriented languages?
Consider python. In python making something private is explicitly only an agreement between the author and the user, but nothing prevents a user from accessing private members. Though, they should not. C++ isn't that explicit about saying "if you want you can access private members", but still it is possible with some effort. Nevertheless you should not. C++ does not prevent you from shooting yourself in your foot and accessing private members is one way of doing that. It isn't the case in your example, but typically accessing private members directly will break the object beyond repair.
does this mean access modifiers are useless?
I'll repeat my comment: Is a traffic light useless? I mean when it is red I can still cross the street. Access specifiers are not there to prevent you from doing something wrong by all means, they are to help you to avoid doing something wrong (and if you try hard you can still do something wrong).
and is there anything we can do to prevent such thing to happen?
Declaring a member as private is enough to signal that a user should not access the member directly by any means. If someone wants to break that agreement then they can do it. You cannot prevent a user from doing something wrong. If they want to break your class they can do so. However, it is not your responsibility to guarantee that something broken still works as expected. If a user bypasses access specifiers then they broke the agreement between them and you. Consider you buy a laptop and throw it out of the window from 42th floor. Will you complain to the manufacturer that afterwards the laptop is not working properly anymore? I guess no, instead you will understand that you made something wrong with using your laptop.
PS: Your last two examples are undefined behavior. reinterpret_cast is not a way to cast between arbitrary types magically. The set of allowed casts and what you can do with the results is in fact rather limited (see here). Also a c-style cast enables you do to casts that can be very wrong, without your compiler complaining about it. Thats why they should be avoided in favor of the proper c++ casts (static_cast et al).
Answering your question 4, is there anything we can do to prevent such thing to happen?:
It is indeed a language design problem that code using a class in C++ is typically able to see the inner makeup of a class. A visible, complete class definition is clearly a breach of information hiding. It's necessary though because of the "by-value semantics" of C++ that it inherited from C and which distinguishes it from, say, C# or Java.
One of the consequences is what you describe: That users can easier access object data they are not opposed to. (To be fair, with enough malicious energy that is unpreventable in the general sense no matter the precautions, but knowing the class layout allows you to do so with less "criminal effort", in this case through normal language means. Another, even simpler way which I recall was buried in one large project when it went open source was to simply #define private public before including the header in question.)
A second, more relevant problem is that code which uses objects of that class, or one of its descendants, is too tightly coupled with that class; it knows more than it should or needs to. Any trivial change to the class makes it necessary to recompile all code which includes its definition, directly or indirectly. For large projects with elaborate class hierarchies touching a base class may cause a senseless re-build of the whole project.1
To finally answer your question: The canonical C++ strategy to reduce this coupling are compilation firewalls. You essentially define an interface of pure virtual functions and no data, which should be relatively stable. User code sees only that interface. By that you gain information hiding and the power of polymorphism. But because you cannot directly handle objects any longer but only pointers or references, you lose the advantages of C++'s by-value paradigm (speed, no aliasing).
1 In a job interview in 1998 or so as C++ developer at Star Division, which was developing StarOffice, the original precursor to OpenOffice and LibreOffice, I was asked: "You have a base class, directly or indirectly used throughout the project. Now you would like to add a virtual function to it but avoid recompilation of the whole project, because it would just take too long. Can you do that? How?" The answer is that most implementations probably maintain the virtual functions in a vtable to which you can append without changing the offsets of existing functions (and, of course, without altering the object layout). Obviously, there is no guarantee that the implementation does not generate the vtable backwards, or employs some other mechanism, but in practice that's what you can do.
So i have made this task system, or i'm trying to make it, so that if someone has done more than seven tasks, the next person has to do seven.
the doubles (Bodine, Finn, Tycho) are the persons. Now i dont know if i used it correctly,(if they need to be doubles) and how i use them in this line of code:
if (taskNumbers == 7)
{
std::cout << "It's " + /*what to do here? */ + "time!";
what i want is that if the task numbers are higher than seven, and Bodine has done 7 tasks, it says "Its finn his time!"
#include <iostream>
using namespace std;
double Bodine;
double Finn;
double Tycho;
bool tasksDone = true;
int taskNumbers = 0;
int main()
{
if (taskNumbers > 7)
{
std::cout << "It's " + /*what to do here? */ + "time!";
return 1;
}
}
This is one of my first projects yet(im 13, so yeah....). I only need to know how to use the next variable!
Well, you're not really keeping track of whose turn it is. Also, double is not the right tool for the job here. I would do something like this:
std::vector<std::string> users = { "Bodine", "Finn", "Tycho" };
int currentUser = 0;
Now we have a list of names as well as a counter that says whose turn it is. 0 is for the first person, 1 for the second person, and 2 for the third person. Then we need the logic to advance that counter:
void advanceUser() {
if (++currentUser >= users.size()) {
currentUser = 0;
}
}
This increases currentUser, and when it is bigger than the amount of people, it loops back to 0, so the people take turns correctly.
Next about the task logic, I would suggest something like this:
int tasksDone = 0;
void incrementTasksDone() {
if (++tasksDone >= 7) {
advanceUser();
std::cout << "It's " + users[currentUser] +"'s time!" << std::endl;
tasksDone = 0;
}
}
Here we have a counter that keeps track of the amount of tasks, and when it hits 7, it sets it back to 0 and announces that it's the next user's turn after callling advanceUser to set that right.
You can then call incrementTasksDone however you want, for instance I tested it like this:
int main()
{
for (int i = 0; i < 100; i++) {
incrementTasksDone();
}
}
That would get 100 tasks done, for instance, so the turns would change 14 times in the process and it would print the message every time.
Also, to get this example to run, be sure to add the following includes:
#include <vector>
#include <string>
#include <iostream>
Hello and welcome to C++. Many will say that C++ is a bad language of choice for starters. Well I started with C++ myself a long time ago and I'm still learning to this day. I am 100% self taught and I didn't have the advantages that people do today with the amount of information that is available over the internet as well as the modern formats that are available. When I first started learning C++, the internet was in the stage of booming; but most websites back then were nearly pure text and even simple pictures or graphics took a while to load to the screen because this was in the era of Dial Up. Today, people who are starting out have the advantages of both this website, other similar sites and even youtube videos. However, I still enjoy helping where I can because it doesn't only help you, but it also helps me to improve what I have already learned. C++ has evolved over the years so what I'll do here is demonstrate to you a small application that I believe mimics the behavior of what you have described you are trying to do. Some of these techniques are a little advanced especially for beginners, however I think it is a good fit that someone who is new learns these concepts early.
Storage Types & Lifetime - There are basically 4 main storage types in C++: Automatic, Dynamic, Static and Thread. I mostly focus on the first 3.
Automatic: It has the lifetime of the scope that it is declared in and will automatically be destroyed once that scope exits from its closing brace }
Dynamic: Memory that is represented by pointers but declared with new and must have a matching delete, or arrays new[] and delete[] respectively. They can live longer than the scope they are declared in. They will live until their matching delete is called. If no matching delete takes place, this leads to a memory leak, invalid pointers, dangling pointers & references and undefined behavior. Special Care needs to be taken when using raw-pointers; it's advisable to use either containers or smart pointers.
Static: These are typically found in the global namespace and or global filespace. If a static is declared in the main.cpp it will have a lifetime of the application and the scope of the whole program. If they are declared in other cpp files, they will have the scope of that file, unless if they are declared in some header file, then they will have the scope of what other translation unit includes that header. They are similar to Automatic in the sense they will automatically be destroyed, but they differ as in the fact that they are initialized only once, the maintain their state and you can only have a single instance of them.
For a demonstration of the different types of storage classifications you can see my previous answer to this Q/A.
Classes and Inheritance: - (I will not involve Polymorphism).
Classes:
Classes and Structs are user defined data types.
The difference between the two is the default access
By default: Structs have Public Members & Classes have Private Members
Members of a Class or Struct can be any built in type, pointers to types, another user defined data type, and methods or functions.
Member Variables can be of any Storage Type: Automatic, Dynamic, Static and Thread, however, member functions are usually Automatic but can be Static. If you have a member variable it has to be initialized outside of the Class's Declaration to resolve it's symbols.
They have Constructors and Destructors by default, but one can create their own custom Constructor or Destructor. You will typically see people mention them by their short names: ctor & dtor respectively.
One class can inherit from another.
Inheritance:
You have Base or Super Classes, and you have Derived or Child Classes
When Inheriting from a Base class, if the Base class's ctor is Public, this means you can create an object of both the Base & Derived Classes. Sometimes you want this kind of design, but there are times when you don't.
If you do not want the user to be able to create an instance of the Base Class, but are able to create an instance of the Derived Class then all you need to do is make sure that 1st its ctor is Protected. If you declare it Private, then even your Derived Classes can not access it and therefore they can not be declared. *2nd You want to make sure that your Destructors - dtors are Virtual; otherwise you will have issues with the order in which classes are destroyed.
A Base Class can have Member Functions that are Virtual which means All Derived Classes must Implement that Function.
A Base Class can have Member Functions that are Purely Virtual which is similar above, but also prevents anyone from declaring an instance of this Base Class because this makes the Base Class Abstract which means it's an idea for an interface.
Example Virtual Function: virtual update();
Example Purely Virtual : `virtual update() = 0;
Containers - (I will use a std::container, but I won't involve any Algorithms)
There are many kinds of containers for different needs; these containers help to keep your program simple, easy to manage and debug, user friendly and to prevent many future headaches instead of using basic C Arrays.
There are a few different types and they each have their own purpose and properties which I will not go over all of their details as you can find a plethora of information on the net regarding them, but I will label and group them to their similar properties: The groupings are Sequence Containers, Associative Containers, & Unordered Containers and these all belong to the std:: namespace. The major difference between the groupings are: Sequence Containers are like arrays and linked lists, Associative Containers are binary trees, and Unordered Containers are similar to the binary trees except they are not in order, they are considered hash tables.
Sequence: vector, dequeue, queue, list, forward_list, array
Associative: set, multiset, map, multimap
Unordered: unordered_set, unordered_multiset, unordered_map, unordered_multimap`
What makes these containers powerful is the ability to either traverse them quickly or to insert and find quickly depending on which container you are using. Another good feature is helping in the process of memory management. Finally is the numerous algorithms and iterators that can do work on them.
For more information on the stl I advise to watch this youtube series by Bo Qian
Smart Pointers There are a few different types, but the most important two are std::shared_ptr<T> and std::unique_ptr<T>. The major difference between the two is that shared has a reference count to how many objects have access to it; it means it has a public type interface and can use copy semantics; unique on the other hand has only 1 owner at a time, it can transfer ownership but once it does the original owner no longer has access to it. You can not copy unique, but you can move them. These smart pointers help with the use of dynamic memory and life time object management to prevent memory leaks, invalid pointers, dangling pointers and references, undefined behavior etc. They help to minimize the use of new & delete and new[] & delete[] respectively.
Now that you have an understanding of some of the concepts that you will see quite often in C++ programs; I will now show you the simple application that I have written based off of what it was that I perceived you were trying to do:
#include <array>
#include <exception>
#include <memory>
#include <string>
#include <iostream>
class Person {
protected:
std::string name_;
int tasksCompleted_;
public:
const std::string& whoIs() const { return name_; }
int numberTasksCompleted() const { return tasksCompleted_; }
virtual void performTask() = 0;
protected:
explicit Person(const std::string& name) : name_{ std::move(name) } {}
virtual ~Person() = default;
};
class Bodine final : public Person {
private:
static int currentTask;
public:
explicit Bodine(const std::string& name = std::string("Bodine")) : Person(name) {}
virtual ~Bodine() = default;
virtual void performTask() { currentTask++; tasksCompleted_ = currentTask; }
};
int Bodine::currentTask = 0;
class Finn final : public Person {
private:
static int currentTask;
public:
explicit Finn(const std::string& name = std::string("Finn")) : Person(name) {}
virtual ~Finn() = default;
virtual void performTask() { currentTask++; tasksCompleted_ = currentTask; }
};
int Finn::currentTask = 0;
class Tycho final : public Person {
private:
static int currentTask;
public:
explicit Tycho(const std::string& name = std::string("Tycho")) : Person(name) {}
virtual ~Tycho() = default;
virtual void performTask() { currentTask++; tasksCompleted_ = currentTask; }
};
int Tycho::currentTask = 0;
int main() {
try {
std::array<std::shared_ptr<Person>, 3> people{
std::shared_ptr<Person>(new Bodine()),
std::shared_ptr<Person>(new Finn()),
std::shared_ptr<Person>(new Tycho())
};
// For each person in array
const int MAX_TASKS = 7;
int currentPerson = 0;
for (auto& p : people) {
std::cout << p->whoIs() << " has performed task #:\n";
while (p->numberTasksCompleted() < 7) {
p->performTask();
std::cout << p->numberTasksCompleted() << '\n';
if (p->numberTasksCompleted() == MAX_TASKS) {
currentPerson++;
if (currentPerson <= (people.size() - 1) ) {
std::cout << "It's your turn " << people[currentPerson]->whoIs() << " to do some tasks.\n";
}
break;
}
}
}
} catch( std::runtime_error& e ) {
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
-Output-
Bodine has performed task #:
1
2
3
4
5
6
7
Finn has performed task #:
1
2
3
4
5
6
7
Tycho has performed task #:
1
2
3
4
5
6
7
Link to above program.
I know this is quite a bit to read; but please look closely over the application to try and see what it is doing. You can copy and paste this right into your own IDE and try to build and run it to see it in action. Hopefully this will give you a grasp of what C++ is.
-Edit-
About the code above: it is a little more complex than what it truly needs to be. First off you would not really see a single person's identity as it's own class. I only did this to demonstrate what inheritance is. I choose to use std::array instead of std::vector mainly due to the fact that you had 3 specific people from your proposal. If you have an unknown amount of people at compile time then std::vector would be the correct container to use. You wouldn't really need to use inheritance either. Also if you look at the 3 derived classes there is a lot of duplicate code. I also used shared_ptrs to show how they can be used, but are not necessary in your specific situation. I also used static member variables in the Derived Classes to show static storage.
Here is a simplified version of the above code to mimic your behavior. Both programs do the same task similarly, the only major differences are: The use of inheritance with purely virtual methods, static member storage, container type, and dynamic memory via the use of shared_ptr.
#include <exception>
#include <iostream>
#include <string>
#include <vector>
class Person {
private:
std::string name_;
int tasksCompleted_;
public:
explicit Person(const std::string& name) : name_(name), tasksCompleted_(0) {}
const std::string& whoIs() const { return name_; }
int numberTasksCompleted() const { return tasksCompleted_; }
void performTask() { tasksCompleted_++; }
};
int main() {
try {
std::vector<Person> people{
Person( "Bodine" ),
Person( "Finn" ),
Person( "Tycho" )
};
// For each person in array
const int MAX_TASKS = 7; // Don't like magic numbers!
int currentPerson = 0; // Needed variable
for (auto& p : people) {
std::cout << p.whoIs() << " has performed task #:\n";
while (p.numberTasksCompleted() < MAX_TASKS) {
p.performTask();
std::cout << p.numberTasksCompleted() << '\n';
if (p.numberTasksCompleted() == MAX_TASKS) {
currentPerson++;
if (currentPerson <= (people.size() - 1) ) {
std::cout << "It's your turn " << people[currentPerson].whoIs() << " to do some tasks.\n";
}
break;
}
}
}
} catch( std::runtime_error& e ) {
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
Here is the link for this version of the program!
Inside this following thread routine :
void* Nibbler::moveRoutine(void* attr)
{
[...]
Nibbler* game = static_cast<Nibbler*>(attr);
while (game->_continue == true)
{
std::cout << game->_snake->_body.front()->getX() << std::endl; // display 0
std::cout << game->getDirection() << std::endl; // display 0
game->moveSnake();
std::cout << game->_snake->_body.front()->getX() << std::endl; // display 0
std::cout << game->getDirection() << std::endl; // display 42
}
}
[...]
}
I am calling the member function moveSnake(), which is supposed to modify the positions of the cells forming my snake's body.
void Nibbler::moveSnake()
{
[...]
std::cout << this->_snake->_body.front()->getX() << std::endl; // display 0
this->_snake->_body.front()->setX(3);
this->_direction = 42;
std::cout << this->_snake->_body.front()->getX() << std::endl; // display 3
[...]
}
Although my two coordinates are effectively modified inside my moveSnake() function, they are not anymore when I go back to my routine, where they keep their initial value. I don't understand why this is happening, since if I try to modify any other value of my class inside my moveSnake() function, the instance is modified and it will keep this value back in the routine.
The Nibbler class :
class Nibbler
{
public :
[...]
void moveSnake();
static void* moveRoutine(void*);
private :
[...]
int _direction
Snake* _snake;
IGraphLib* _lib;
pthread_t _moveThread;
...
};
The snake :
class Snake
{
public :
[...]
std::vector<Cell*> _body;
};
And finally the cell :
class Cell
{
public :
void setX(const int&);
void setY(const int&);
int getX() const;
int getY() const;
Cell(const int&, const int&);
~Cell();
private :
int _x;
int _y;
};
The cell.cpp code :
void Cell::setX(const int& x)
{
this->_x = x;
}
void Cell::setY(const int& y)
{
this->_y = y;
}
int Cell::getX() const
{
return this->_x;
}
int Cell::getY() const
{
return this->_y;
}
Cell::Cell(const int& x, const int& y)
{
this->_x = x;
this->_y = y;
}
Cell::~Cell()
{}
On its face, your question ("why does this member not get modified when it should?") seems reasonable. The design intent of what has been shown is clear enough and I think it matches what you have described. However, other elements of your program have conspired to make it not so.
One thing that may plague you is Undefined Behavior. Believe it or not, even the most experienced C++ developers run afoul of UB occasionally. Also, stack and heap corruption are extremely easy ways to cause terribly difficult-to-isolate problems. You have several things to turn to in order to root it out:
Debuggers (START HERE!)
with a simple single-step debugger, you can walk through your code and check your assumptions at every turn. Set a breakpoint, execute until, check the state of memory/variables, bisect the problem space again, iterate.
Static analysis
Starting with compiler warnings and moving up to lint and sophisticated commercial tools, static analysis can help point out "code smell" that may not necessarily be UB, but could be dead code or other places where your code likely doesn't do what you think it does.
Have you ignored the errors returned by the library/OS you're making calls into? In your case, it seems as if you're manipulating the memory directly, but this is a frequent source of mismatch between expectations and reality.
Do you have a rubber duck handy?
Dynamic analysis
Tools like Electric Fence/Purify/Valgrind(memcheck, helgrind)/Address-Sanitizer, Thread-Sanitizer/mudflaps can help identify areas where you've written to memory outside of what's been allocated.
If you haven't used a debugger yet, that's your first step. If you've never used one before, now is the time when you must take a brief timeout and learn how. If you plan on making it beyond this level, you will be thankful that you did.
If you're developing on Windows, there's a good chance you're using Visual Studio. The debugger is likely well-integrated into your IDE. Fire it up!
If you are developing on linux/BSD/OSX, you either have access to gdb or XCode, both of which should be simple enough for this problem. Read a tutorial, watch a video, do whatever it takes and get that debugger rolling. You may quickly discover that your code has been modifying one instance of Snake and printing out the contents of another (or something similarly frustrating).
If you can't duplicate the problem condition when you use a debugger, CONGRATULATIONS! You have found a heisenbug. It likely indicates a race condition, and that information alone will help you hone in on the source of the problem.