C++ introduces some differences and extensions from the classic C struct as well as commonality and differences with the C++ class. The traditional C struct is a grouping of variables in memory with no extra capabilities. C++ allows for some extended capabilities to the struct which were once the domain of the C++ class. This includes contructor/destructor and member functions.
- # C++ Struct Examples
- # C Struct, C++ Struct and Class differences
- # C++ Struct and Virtual Functions
Simple example of a program using C++, a structure and a constructor:
// File: struct-test.cpp // // This example shows the use of a structure in C++ and how it behaves much // like a class including the use of a contructor yet maintains the useability // of a regular C structure. #include <iostream> #include <string> using namespace std; main() { struct DataElement { string SVal; int iVal; bool hasData; DataElement() // Example of a constructor used in a structure. { iVal=-1; hasData=0; } } *RealData; RealData = new DataElement [ 5 ]; // Assignment RealData[0].SVal = "Value loaded into first structure element."; RealData[0].hasData = 1; // True cout << "First element 0: " << RealData[0].SVal << endl; cout << " " << RealData[0].hasData << endl; cout << "Second element 1: " << RealData[1].SVal << endl; cout << " " << RealData[1].hasData << endl; // Show effect of contructor cout << " " << RealData[1].iVal << endl; // Show effect of contructor delete [] RealData; // Or: delete [5] RealData; }
Compile: g++ struct-test.cpp
[Potential Pitfall]: In Red Hat Linux versions 7.x one could omit the "using namespace std;" statement. Use of this statement is good programming practice and is required in Red Hat 8.0.
[Potential Pitfall]: Red Hat 8.0 requires the reference to "#include <fstream>". Red Hat versions 7.x used "#include <fstream.h>".
Output: ./a.out
First element 0: Value loaded into first structure element. 1 Second element 1: 0 -1
The same example using typedef and a C++ initializer:
#include <iostream> #include <string> using namespace std; typedef struct dataElement { string SVal; int iVal; bool hasData; dataElement() // Example of a constructor used in a structure. : iVal(-1), hasData(0) {} } DataElement; main() { DataElement *RealData; RealData = new DataElement [ 5 ]; RealData[0].SVal = "Value loaded into first structure element."; RealData[0].hasData = 1; // True cout << "First element 0: " << RealData[0].SVal << endl; cout << " " << RealData[0].hasData << endl; cout << "Second element 1: " << RealData[1].SVal << endl; cout << " " << RealData[1].hasData << endl; // Show effect of contructor cout << " " << RealData[1].iVal << endl; // Show effect of contructor delete [] RealData; // Or: delete [5] RealData; }
Identical results as above example.
By contrast the typical "C" structure initialization is as follows:
#include <stdio.h> typedef struct dataElement { char *cVal; int iVal; } DataElement; main() { DataElement RealData = {"Text goes here", 5 }; // "C" style initialization. printf("%s \nInteger value=%d\n",RealData.cVal,RealData.iVal); }
Run: ./struct-simple
Text goes here Integer value=5
C++ structure notes:
- The struct can employ a constructor to initalize variables.
- The struct can employ a destructor.
- The structure constructor can not be declared virtual.
- Structure member variables are public by default.
When using the GNU C++ compiler, the C++ struct and class behave the same. While the C struct using an ANSI C compiler supports only variables, the GNU C++ (g++) compiler will support struct member functions, even virtual functions as well as public and private access controls and inheritance.
Features | ANSI C struct | C++ struct | C++ class |
---|---|---|---|
member variables | yes | yes | yes |
member functions | no | yes | yes |
constructor support | no | yes | yes |
destructor support | no | yes | yes |
access control | no | yes | yes |
default access | public | public | private |
inheritance | no | yes | yes |
The only difference between a C++ struct and a C++ class is that a struct defaults members to public while a class defaults to private.
struct ABC { int x; }; main() { ABC abc; abc.x = 5; } |
class ABC { int x; }; main() { ABC abc; abc.x = 5; } |
$ g++ test.cpp $ |
$ g++ test.cpp test.cpp: In function ‘int main()’: test.cpp:2:7: error: ‘int ABC::x’ is private test.cpp:7:8: error: within this context |
In memory, the C++ struct will behave just like the C struct until a virtual function is used, at which point the compiler will prefix the struct's memory with a pointer to the virtual table. This is important especially when reading and writing structs across the network or to binary files as this prefixed pointer may have unintended consequences. Also a pointer to the struct with a virtual function will not be a pointer to the first variable in the struct as the first element in the struct will be the pointer to the virtual table. Also note that this pointer will reflect the word size of the hardware. To mimic the ANSI C behavior of a struct, a C++ struct with a virtual function will require a serialization process to be programmed into the computional logic and flow or avoid the use of virtual functions.
Compilers other than GNU C++ are free to implement the memory layout differently, so proper serialization of data will be required before exchanging a C++ struct or object with other systems. It is also permissible that other systems may rearrange the order of variables based on private/protected/public access definitions.
Memory order:- A pointer to the virtual functions table is added only when the class has virtual methods. Pointer size will be dependent on hardware and software word size. A 32 bit system will prefix a 32 bit pointer to the vtable while a 64 bit system will prefix a 64 bit pointer to the vtable.
- Base class variable data
- Class member variable data
The following example shows the memory layout of an object with class inheritance:
#include <iostream> using namespace std; struct AAA { public: int aaa; AAA():aaa(0xff){}; AAA(int _aaa){aaa = _aaa;} void printThis(){ cout << " Object this pointer memory: " << hex << (size_t)this << endl;} }; struct BBB : AAA { int bbb; BBB():bbb(0x0000){}; BBB(int _aaa, int _bbb){aaa=_aaa;bbb=_bbb;}; }; main() { AAA aaa(0xffff);; BBB bbb; // AAA Print pointers - memory address of object and first member of object cout << "AAA: " << sizeof(AAA) << " bytes." << endl; cout << " Object memory address location &aaa: " << hex << (size_t)&aaa << endl; cout << " First member variable Memory aaa.aaa: " << hex << (size_t)&aaa.aaa << endl; aaa.printThis(); // Print object this pointer cout << "BBB: " << sizeof(BBB) << " bytes." << endl; cout << " Object memory address location &bbb: " << hex << (size_t)&bbb << endl; cout << " First inherited member memory bbb.aaa: " << hex << (size_t)&bbb.aaa << endl; cout << " First member variable Memory bbb.bbb: " << hex << (size_t)&bbb.bbb << endl; bbb.printThis(); }
AAA: 4 bytes. Object memory address location &aaa: 7fffe3a8c410 First member variable Memory aaa.aaa: 7fffe3a8c410 Object this pointer memory: 7fffe3a8c410 BBB: 8 bytes. Object memory address location &bbb: 7fffe3a8c400 First inherited member memory bbb.aaa: 7fffe3a8c400 First member variable Memory bbb.bbb: 7fffe3a8c404 -- BBB begins after AAA which 4 bytes in size Object this pointer memory: 7fffe3a8c400The beginning of BBB memory is occupied by AAA from which it inherits data variables.
The following example shows the memory layout effect of adding a virtual function and the accompanying vtable pointer prepended to the object's memory footprint:
#include <iostream> using namespace std; struct AAA { public: int aaa; AAA():aaa(0xff){}; AAA(int _aaa){aaa = _aaa;} void printThis(){ cout << " Object this pointer memory: " << hex << (size_t)this << endl;} virtual void functionA(){}; }; struct BBB : AAA { int bbb; BBB():bbb(0x0000){}; BBB(int _aaa, int _bbb){aaa=_aaa;bbb=_bbb;}; void functionA(){}; }; main() { AAA aaa(0xffff);; BBB bbb; // AAA Print pointers - memory address of object and first member of object cout << "AAA: " << sizeof(AAA) << " bytes." << endl; cout << " Object memory address location &aaa: " << hex << (size_t)&aaa << endl; cout << " First member variable Memory aaa.aaa: " << hex << (size_t)&aaa.aaa << endl; aaa.printThis(); // Print object this pointer cout << "BBB: " << sizeof(BBB) << " bytes." << endl; cout << " Object memory address location &bbb: " << hex << (size_t)&bbb << endl; cout << " First inherited member memory bbb.aaa: " << hex << (size_t)&bbb.aaa << endl; cout << " First member variable Memory bbb.bbb: " << hex << (size_t)&bbb.bbb << endl; bbb.printThis(); }
AAA: 16 bytes. Object memory address location &aaa: 7fff4b1d7fb0 First member variable Memory aaa.aaa: 7fff4b1d7fb8 -- 8 bytes offset for vtable pointer Object this pointer memory: 7fff4b1d7fb0 BBB: 10 bytes. Object memory address location &bbb: 7fff4b1d7fc0 First inherited member memory bbb.aaa: 7fff4b1d7fc8 -- 8 byte offset for vtable pointer First member variable Memory bbb.bbb: 7fff4b1d7fcc -- 4 bytes additional offset for actual size of AAA Object this pointer memory: 7fff4b1d7fc0Note that sizeof() does not work properly when used on a struct with a virtual function (and thus with a vtable pointer).
Note that the starting memory address of the object is not the same as that for the first member variable. This is due to the vtable pointer which consumes 8 bytes for the pointer on a 64 bit system. The position of the begining variables has shifted by these 8 bytes.
The following example shows how a "C" union can be used with a constructor to initialize data.
#include <stdio.h> typedef union uAA { double dVal; int iVal[2]; uAA() : dVal(3.22) {} } UAA; main() { UAA rdata; printf("Array output: %d %d \nDouble output: %lf \n", rdata.iVal[0], rdata.iVal[1], rdata.dVal); }
Run: ./union-test
Array output: 1546188227 1074381455 Double output: 3.220000
[Potential Pitfall]: The C++ difference - Structures used with union. A struct can NOT be used with a constructor if it is to be used in a union. The C++ recognition of the constructor in a struct converts it to a C++ class which is not valid in a "C" union.
Typical use of a "C" union:
#include <stdio.h> typedef struct { int iVal1; int iVal2; } DataElement; typedef union { DataElement de; int iVal[2]; } UAA; main() { UAA rdata; rdata.de.iVal1 = 0; rdata.de.iVal2 = 1; printf("Array output: %d %d \n", rdata.iVal[0], rdata.iVal[1]); } Note: Compiles properly with the gcc and g++ compiler. Run: ./union-test Array output: 0 1 |
Use of C++ structure (with constructor) in a union:
#include <stdio.h> typedef struct dataElement { int iVal1; int iVal2; dataElement() : iVal1(1), iVal2(2){}; } DataElement; typedef union { DataElement de; int iVal[2]; } UAA; main() { UAA rdata; printf("Array output: %d %d \n", rdata.iVal[0], rdata.iVal[1]); }
union-test.cpp:13: error: member `DataElement |
C++ union notes:
- The union can employ a constructor to initalize variables.
- The union can employ a destructor.
- Union member functions can NOT be declared virtual.
- Union member variables are public by default.
- A union's data members can NOT be declared static.
- A union can not be used as a base class.
Note that structures can have endian byte order issues depending on the hardware used. See Endian Byte Order, big vs little endian and byte swapping for more information.
Why a tutorial on the seemingly simple subject of structs and unions? While it at first may appear simple, the comlete coverage has been overlooked by every C++ book I have ever seen. Many programmers I have met didn't know that constructors could be used with a C structure. I didn't know until I was told by another programmer. It was not covered by my professor nor was it in our very thourough C++ text book. This is just an FYI.