C++ is fast and efficient - partly due to how it allocates continuous memory with as few gaps as possible. The declaration files are referenced by other parts of the code because it tells the compiler how memory is laid out. This means that the implementation is completely hidden, but private fields aren't truly private.
Suppose I have just written a simple class to prove that stack allocated objects are destroyed when they go out of scope.
// Program.cpp
#include "stdafx.h"
#include "MyClass.h"
using MClass = MyNamespace::MyClass<int>;
int main()
{
for (int n = 0; n < 8; n++) {
MClass m = MClass();
m.PrintWithCount();
}
getchar();
return 0;
}
MyClass must be declared in the "MyClass.h" file.
// MyClass.h
#pragma once
#ifndef MY_CLASS_H
#define MY_CLASS_H
#include <string>
namespace MyNamespace {
template<typename T>
class MyProp {
public:
MyProp() : MyProp<T>(0) {} // required default ctor
MyProp(T v) { this->val = v; }
T get_Val() const { return this->val; }
private:
T val;
};
template<typename T>
class MyClass {
public:
MyClass();
// Prints info on the object
void Print() const;
// Prints info on the object
// and how many other objects are in memory
void PrintWithCount() const;
// Destroying the object reduces the count
~MyClass();
private:
MyProp<T> prop;
std::string time;
};
}
#endif // !MY_CLASS_H
This is pretty straight forward. MyProp is defined in the .h file and has no corresponding .cpp file, but MyClass still needs a .cpp file for definition. This is where a lot of the heavy lifting happens - so pay close attention.
// MyClass.cpp
#include "stdafx.h"
#include <iostream>
#include <string>
#include <stdio.h>
#include <time.h>
#include "MyClass.h"
/*
* Gets the current date and time as a string
*/
const std::string currentDateTime() {
time_t now = time(0);
char buf[80];
tm tstruct;
localtime_s(&tstruct, &now);
// Get current date/time, format is YYYY-MM-DD.HH:mm:ss
strftime(buf, sizeof(buf), "%Y-%m-%d.%X", &tstruct);
return buf;
}
namespace MyNamespace {
// fileprivate variables
int myclass_id = 0;
int myclass_count = 0;
template<typename T>
MyClass<T>::MyClass()
{
myclass_count++;
myclass_id++;
this->prop = MyProp<T>(myclass_id);
this->time = currentDateTime();
}
template<typename T>
struct MyProp_def {
T val;
};
template<typename T>
struct MyClass_def {
MyProp<T> id;
std::string time;
};
template<typename T>
void print_my_class(const MyClass<T> *const t) {
MyClass_def<T> c = ((MyClass_def<T>*)t)[0];
MyProp_def<T> p = ((MyProp_def<T>*)(&(c.id)))[0];
printf("MyClass(%d) (created on %s)", p.val, c.time.c_str());
}
template<typename T>
void MyClass<T>::Print() const
{
print_my_class<T>(this);
printf("\n");
}
template<typename T>
void MyClass<T>::PrintWithCount() const
{
print_my_class(this); // <T> is not needed by some compilers
printf(" is part of %d objects\n", myclass_count);
}
template<typename T>
MyClass<T>::~MyClass()
{
myclass_count--;
}
template MyClass<int>;
}
The application compiles and runs, producing the following output:
MyClass(1) (created on 2017-09-11.01:55:39) is part of 1 objects MyClass(2) (created on 2017-09-11.01:55:39) is part of 1 objects MyClass(3) (created on 2017-09-11.01:55:39) is part of 1 objects MyClass(4) (created on 2017-09-11.01:55:39) is part of 1 objects MyClass(5) (created on 2017-09-11.01:55:39) is part of 1 objects MyClass(6) (created on 2017-09-11.01:55:39) is part of 1 objects MyClass(7) (created on 2017-09-11.01:55:39) is part of 1 objects MyClass(8) (created on 2017-09-11.01:55:39) is part of 1 objects
We can see that inside each loop, there is only one object in memory. Good: this means that object destruction is taking place before the next loop - when the object goes out of scope. That is one of the great features of stack allocated objects.
The file-private method print_my_class is displaying private fields of the object! This is done because of the consistency by which the MyClass object is sequentially laid out in memory. While the printf statement isn't very complicated, imagine how convenient this must be for code re-use for more complicated logic. I've actually done this for some pretty complicated/lengthy methods (it's a dirty way to stay const-compliant), but this is an article, not source code.
Speaking of code, let's get back to it... We were discussing this snipplet here:
template<typename T>
struct MyProp_def {
T val;
};
template<typename T>
struct MyClass_def {
MyProp<T> id;
std::string time;
};
template<typename T>
void print_my_class(const MyClass<T> *const t) {
MyClass_def<T> c = ((MyClass_def<T>*)t)[0];
MyProp_def<T> p = ((MyProp_def<T>*)(&(c.id)))[0];
printf("MyClass(%d) (created on %s)", p.val, c.time.c_str());
}
Let's break it down:
template<typename T>
void print_my_class(const MyClass<T> *const t) {
// Get the value at the pointer
MyClass<T> value_of_t = *t;
// Cast the reference to the value as a pointer
MyClass_def<T>* pointer_to_struct_of_t = (MyClass_def<T>*)(&value_of_t);
// Get the value at the pointer
MyClass_def<T> struct_of_t = pointer_to_struct_of_t[0];
// can also be written
// MyClass_def<T> struct_of_t = *pointer_to_struct_of_t;
// but it's preference... whichever is easier to read
// Continue the code as usual
MyClass_def<T> c = struct_of_t;
// The same logic could be applied here, but there's an easier way
MyProp_def<T> p = ((MyProp_def<T>*)(&(c.id)))[0];
// Use the values provided by the defined structs.
printf("MyClass(%d) (created on %s)", p.val, c.time.c_str());
}
As stated in the code comments, there is an easier way. Even better, the code snipplet can be restructured as follows:
template<typename T>
struct MyProp_def {
T val;
};
template<typename T>
struct MyClass_def {
MyProp_def<T> id;
std::string time;
};
template<typename T>
void print_my_class(const MyClass<T> *const t) {
MyClass_def<T> c = *((MyClass_def<T>*)t);
printf("MyClass(%d) (created on %s)", c.id.val, c.time.c_str());
}
The application compiles and runs, producing the following output:
MyClass(1) (created on 2017-09-11.01:55:39) is part of 1 objects MyClass(2) (created on 2017-09-11.01:55:39) is part of 1 objects MyClass(3) (created on 2017-09-11.01:55:39) is part of 1 objects MyClass(4) (created on 2017-09-11.01:55:39) is part of 1 objects MyClass(5) (created on 2017-09-11.01:55:39) is part of 1 objects MyClass(6) (created on 2017-09-11.01:55:39) is part of 1 objects MyClass(7) (created on 2017-09-11.01:55:39) is part of 1 objects MyClass(8) (created on 2017-09-11.01:55:39) is part of 1 objects
I'm seeing something here... Since memory is laid our sequentially, the MyProp class can just be a set of ordered values within the main struct. We can really just write
template<typename T>
struct MyClass_def {
T id;
std::string time;
};
template<typename T>
void print_my_class(const MyClass<T> *const t) {
MyClass_def<T> c = *((MyClass_def<T>*)t);
printf("MyClass(%d) (created on %s)", c.id, c.time.c_str());
}
Another way to write this could be as follows:
template<typename T>
struct MyClass_def {
T id;
std::string time;
};
template<typename T>
void print_my_class(const MyClass<T> *const t) {
MyClass_def<T>* c = (MyClass_def<T>*)t;
printf("MyClass(%d) (created on %s)", c->id, c->time.c_str());
}
It almost makes you wonder why the fields are marked as private at all.