I thought that I should write down some problems I have had with C++ or things that are just nice to remember. I'll put it here in case someone else should be able to get some help from this or if I forget about something. The goal is to keep it very short and simple. The examples are not meant to be run, just to show the basics.
Q. When should I use virtual functions ?
Q. What to do when you want to use #defines in a Qt header file ?
Q. How to get core dumps on Linux ?
Q. Debugging core dump using gdb yields no information, like this:
Q. What is a reentrant function ?
Q. What is the difference of heap and stack ?
Q. What is private/protected inheritance ?
Q. Target compiler can't find it's resources ?
Q. What to not forget when constructing classes containing pointers ?
Q. I want to have my code warning free, but headers from other libraries I include generate a shitload of warnings. What should I do ?
Q. What is a union ?
Q. Can I have a brief overview over different design patterns ?
Q. How to create compare and sort functions for containers ?
Q. What does volatile mean ?
Q. Describe other terms related to thread safety.
Q. char[] vs char*
Q. When should I use virtual functions ?
A. When the invoked function/method should be the one of the object type. Easiest way is to look at this example:
#include <iostream>
using namespace std;
class A
{
public:
virtual void x() { cout << "A::x\n"; }
void y() { cout << "A::y\n"; }
};
class B : public A
{
public:
void x() { cout << "B::x\n"; }
void y() { cout << "B::y\n"; }
};
int main()
{
A* a = new A();
A* ab = new B();
B* b = new B();
a->x(); // Will call A::x
a->y(); // Will call A::y
ab->x(); // Will call B::x !! Where the virtual makes a difference
ab->y(); // Will call A::y
b->x(); // Will call B::x
b->y(); // Will call B::y
delete a;
delete ab;
delete b;
return 0;
}
Q. What to do when you want to use #defines in a Qt header file ?
A. The problem is that Qt's moc ( at least < Qt 4.0 ) does not understand defines. Therefore a solution is to hide away slots and signals in a friend class. That is you take your slots and signals and put them in a new class. This class is pointed to in your original class so when you need to connect to a slot in the class you now need to connect to OriginalClass::pointerToHiddenSlots::slot instead of directly OriginalClass::slot. You also need to remove the Q_OBJECT macro and thus the need to moc this file.
Q. How to get core dumps on Linux ?
A. First make sure that
prctl(PR_GET_DUMPABLE)
returns 1, if not use prctl to set it to 1. You have to
#include <sys/prctl.h>
Next check that
ulimit -c
returns a reasonable value. Otherwise set it to a higher value or unlimited. The generated core dump can now be examined with
gdb binary-file coredumpfile
Q. Debugging core dump using gdb yields no information, like this:
(gdb) bt #0 0xffffe002 in ?? () #1 0x42028c55 in ?? () #2 0x402853da in ?? () #3 0x420277b8 in ?? ()
A. Sometimes gdb reports the core coming from another binary than it really came from. Try the command $ file coredumpfile and you should see the file that generated the coredump after the from statement. Like...
core.4616: ELF 32-bit LSB core file Intel 80386, version 1 (SYSV), SVR4-style, from 'qpe'
where qpe generated the coredump.
Q. What is a reentrant function ?
A. A function must satisfy the following conditions to be reentrant:
A reentrant function can be thought of as a mathematical function.
Read this for more info.
Q. What is the difference of heap and stack ?
A. In short you can say:
STACK - Objects, and other variables, that are declared within any compound statement are created on the stack. When execution leaves the compound statement, the object's destructor is called and the stack memory space reclaimed. So stack-based objects are useful for short term work, but if an object has to exist beyond the end of statement, then they must be created on the heap using the new operator.
HEAP - The heap is a reserve area of memory with a manager that allocates memory for an object's lifetime. The new operator gets memory from the heap manager and the delete operator returns it. The lifetime of a heap allocated object is only limited by the lifetime of the application. For objects that only exist within a function, or generally any compound statement stack memory is better as its has a smaller allocation overhead.
These definitions were found here.
Q. What is private/protected inheritance ?
A. The more common "public inheritance" is expressed as:
class Elephant : public Animal // An Elephant is an Animal
{};
It expresses an isa relationship, as in the example above: An Elephant is an Animal. Private inheritance on the other hand expresses a hasa relationship. For example like this:
class DorsalFin
{
void stabilise();
};
class Dolphin : private DorsalFin // A Dolphin has a DorsalFin
{
using DorsalFin::stabilise;
};
This means that when calling the stabilise function for a Dolphin object, DorsalFin::stabilise() will be called. However normally you would probably use a member variable for this instead:
class Dolphin
{
public:
void stabilise() { m_dorsal_fin->stabilise(); }
private:
DorsalFin* m_dorsal_fin;
};
A difference between using the member variable and private inheritance is that private inheritance only allows for one DorsalFin while a member variable allows for several. Another distinction is that private inheritance allows for access to protected members of the base class and this is it's main use although you should probably not aim for it as it can be confusing.
So what is protected inheritance then ? Protected inheritance allows derived classes of derived classes to know about the inheritance relationship, thus in relationship to the Dolphin example above if you create a subclass that inherits Dolphin and Dolphin inherited DorsalFin by protected inheritance; this new subclass would be able to access DorsalFin's protected functions, which would not be possible if you had used private inheritance.
Q. Target compiler can't find it's resources ?
A. Sometimes when using an arm toolchain you don't want to install it public for the entire system but locally for your current user. But the compiler might be setup to be installed in a specific location. To find out where it wants the programs and libraries you can use:
arm-g++ -print-search-dirs
This will list the expected directories and then you can make sure you put the stuff ( or use symlinks ) in a directory that works.
Q. What to not forget when constructing classes containing pointers ?
A. If you have a class that looks initially looks like this…
class Data
{
public:
Data(char* name); // Assume this constructor copies name to m_name
~Data(); // Assume the destructor frees the memory of m_name
private:
char* m_name;
}
…it's important to remember that code such as…
Data a("testing");
Data b(a);
…might not behave nicely. The m_name pointer in b will point to the same memory as m_name in a due to the default copy constructor that C++ creates for you. This is probably not what you want as that means that if m_name in a is deleted the m_name in b will point to the deleted memory.
There are several ways to solve this. The two most obvious is to forbid copying and assignment by adding their declarations to the private section of the class. And secondly you can of course make sure that the data in m_name is actually copied instead of just the pointer.
class Data
{
public:
Data(char* name); // Assume this constructor copies name to m_name
~Data(); // Assume the destructor frees the memory of m_name
private:
Data(const Data&); // Forbid copying
Data& operator=(const Data&); // Forbid assignment
char* m_name;
You can see some small code sample for copy constructing or you can find more info and solutions about this here.
A. It's common for programmers to just lower the warning level or ignore all warnings, but a better idea is to suppress the warnings coming from the external header files. Unfortunately this is different depending on which compiler you use. The description below is for GCC on Linux. If you use something else you can probably find it easy by googling for it.
The solution is to use wrapper files with a pragma statement for the external includes. So if you in your program include <pinnar.h> which generates a lot of warnings you can put this in a wrapper file for example "_pinnar.h" that reads:
#pragma GCC system_header #include <pinnar.h>
You need to use separate wrapper files as the pragma statement is valid for the rest of the file, thus if you placed it directly in one of your files you would suppress all warnings for your own file.
A. A union is similar to a struct but at any given time it can only contain one object from it's list of members. So all it's members will occupy the same portion of memory. It can look like this:
union MyUnion
{
int i;
double d;
float f;
char c;
};
int main()
{
union MyUnion mu; // In C++ the union keyword is not needed, but for C it is.
mu.i = 10; // Using the union as an int
mu.c = 'a'; // Using the union as a char
// 10 has now been overwritten as the members share memory.
}
Q. Can I have a brief overview over different design patterns ?
Here are the patterns mentioned in this book very briefly.
Converts one interface into another interface. The adapter pattern is used when:
Decouples abstraction from implementation for independent variation. The bridge pattern is used when:
Compose objects into tree structures. The composite pattern is used when:
Note: Remaining patterns will be listed soon. The diagrams were made in dia and exported as SVG plain. The fontsize had to be set to 1.30-1.40 in dia to make it fit in the generated svg. Also note that to be able to see the diagrams you have to use a browser that can handle svg properly. Currently they are not shown correctly in Konqueror or Firefox, but works fine in Opera. I have not tried in any Windows browser yet.
Q. How to create compare and sort functions for containers ?
TODO .
A. In computer programming, a variable or object declared with the volatile keyword may be modified externally from the declaring object. For example, a variable that might be concurrently modified by multiple threads may be declared volatile. Variables declared to be volatile will not be optimized by the compiler because the compiler must assume that their values can change at any time. Note that operations on a volatile variable are still not guaranteed to be atomic.
This answer was taken from Wikipedia, see the full entry here. And note the links there about why volatile is not always what you want.
Q. Describe other terms related to thread safety.
A. Indicators that code is or is not threadsafe:
Different ways to achieve thread safety:
One approach for thread safety is to make changes to private copies of shared data that are atomically updated when you are done with the changes.
A. Directly in the code char[] is not the same as char*. char[] needs to be initialized.
char* cp; // OK, creates a char pointer char[] cna; // FAIL, must be initialized or size specified char ca[10]; // OK, array with space for 10 chars
With initializers…
char* cp = "daniel"; // Depends on compiler, should really be const char* char[] cna = "daniel"; // OK char ca[10] = "daniel"; // OK, "daniel" is shorter than 10 chars
In functions…
/**
* In this function all arguments are treated as char*
* thus it's a good idea to avoid [] as it's just confusing.
*/
void function(char* cp, char[] cna, char[10] ca) {}