about
Bits Objects C++
01/02/2024
0
Bits: C++ Objects
library and user-defined classes and objects
Synopsis:
This page demonstrates uses of C++ User-Defined types and their objects. The purpose is to quickly acquire some familiarity with user-defined types and their implementations.- C++ defines special class methods: copy and move constructors, copy and move assignment operators, and other operators for indexing and comparison etc.
- The compiler will generate constructors and assignment operators if needed and not provided by the class. Those are correct if, and only if, the class's members and bases have correct semantics for those operations. For other cases, like pointer members, developers must either implement them or prohibit them. We will show how in this example.
- Also, this is the first set of examples to partition code into several files. That supports readability, may improve translation times, and makes maintenance significantly easier.
C++ Aggregate Type Structure
-
Inheritance
A C++ class can inherit multiple base class implementations. The memory footprint of this derived class contains the entire footprint of each of its bases. -
Composition
When an instance of a C++ type holds one or more instances of other types, the memory footprint of each composed type lies entirely within the memory footprint of its parent instance. We say the parent composes its child elements. -
Aggregation
C++ types can hold references and native pointers to instances in static, stack, or native heap memory, and smart pointer handles to instances of types stored in the native heap, e.g., std::unique_ptr<T> or std::shared_ptr<T>. When an instance of a type holds a pointer or handle to instances of other types, the parent's memory footprint holds only the pointer or handle, not the entire child instance. We say the parent aggregates the type referenced by the pointer or handle.
Demo Notes
- Rust uses structs to implement objects in the same way that the other languages use classes. Here we will just use the term class for all of them.
1.0 Source Code
- Demonstrations of common std library types, showing both code and output.
- Code declaring a user-defined type, Point4D, is presented in the second block. Much of the technical weight of this Bit is carried by the left and right panels in this block.
- The third block illustrates how Point4D is created and used.
- Next, we show how to create instances of std library types and also Point4D in the native heap and how they are used.
- The next block presents code and discussion for a library of display and analysis functions.
- Finally, we illustrate the program structure for this demonstration. The emphasis here is on how functions and files partition processing for this demonstration.
1.1 Declaration and use of std::library types
/*-------------------------------------------------------------------
Demonstration library types std::string and std::vector<T>
*/
void demo_stdlib() {
print("Demonstrate C++ Objects\n");
showNote("std library types string and vector<T>");
/* create and display std::string object */
auto str = std::string("\"Wile E. Coyote\"");
auto out = std::string("contents of str = ") + str;
print(out);
print("--- showType(str, \"str\"); ---");
showType(str, "str", nl);
/* create and display std::vector<double> */
auto vec = std::vector<double>{ 3.5, 3, 2.5, 2 };
showOp("showType(vec, \"vec\");");
showType(vec, "vec");
std::cout << "\n vec:" << vec;
// equivalent to:
// operator<<(operator<<(std::cout, "\n vec:"), vec);
// uncomment the preceeding line to see the output repeated
showOp("vec[2] = -2.5;");
vec[2] = -2.5;
std::cout << "\n vec:" << vec;
showOp("auto vec2 = vec : copy construction");
/* copy construction */
auto vec2 = vec;
std::cout << "\n vec2:" << vec2;
showOp("vec2[0] = 42;");
vec2[0] = 42;
std::cout << "\n vec2: " << vec2;
std::cout << "\n vec: " << vec;
showNote(
"Copy construction, auto vec2 = vec, creates\n "
"independent instance. So changing target vec2\n "
"has no affect on source vec."
);
showOp("vec = vec2 : copy assignment");
vec = vec2;
// equivalent to:
// vec.operator=(vec2);
// uncomment the preceeding line and observe no change
std::cout << "\n vec: " << vec << "\n";
Demonstrate C++ Objects
--------------------------------------------------
std library types string and vector<T>
--------------------------------------------------
contents of str = "Wile E. Coyote"
--- showType(str, "str"); ---
str type: class std::basic_string<char,struct std:...
size: 40
vector<T>: {
3.5, 3, 2.5, 2
}
--- showType(vec, "vec"); ---
vec type: class std::vector<double,class std::allo...
size: 32
--- vec[2] = -2.5; ---
vec:
vector<T>: {
3.5, 3, -2.5, 2
}
--- auto vec2 = vec : copy construction ---
vec2:
vector<T>: {
3.5, 3, -2.5, 2
}
--- vec2[0] = 42; ---
vec2:
vector<T>: {
42, 3, -2.5, 2
}
vec:
vector<T>: {
3.5, 3, -2.5, 2
}
--------------------------------------------------
Copy construction, auto vec2 = vec, creates
independent instance. So changing target vec2
has no affect on source vec.
--------------------------------------------------
--- vec = vec2 : copy assignment ---
vec: [42, 3, -2.5, 2]
1.2 Point4D Class
/*---------------------------------------------------------
Points.h defines a space-time point class:
- Point4D represents points with three double spatial
coordinates and std::time_t time coordinate.
*/
#pragma warning(disable:4996) // warning about ctime use
#include <iostream>
#include <vector>
#include <chrono>
#include <ctime>
/*---------------------------------------------------------
Point4D class represents a point in a 4-Dimensional
space-time lattice. Simple enough for illustration,
but still useful.
It declares all of the special class methods, most with
default qualifiers to indicate that the compiler will
generate them as needed.
The word ctor is an abbreviation of constructor and dtor
an abbreviation for destructor.
*/
class Point4D {
public:
Point4D(); // void ctor
Point4D(const Point4D& pt) = default; // copy ctor
Point4D(Point4D&& pt) = default; // move ctor
Point4D& operator=(const Point4D& pt) = default;
// copy assignment
Point4D& operator=(Point4D&& pt) = default;
// move assignment
~Point4D() = default; // dtor
std::string timeToString();
void updateTime();
void show();
double& xCoor() { return x; }
double& yCoor() { return y; }
double& zCoor() { return z; }
std::time_t& tCoor() { return t; }
private:
double x;
double y;
double z;
std::time_t t;
};
Point4D::Point4D() {
x = y = z = 0.0;
t = std::time(0);
}
std::string Point4D::timeToString() {
return ctime(&t);
/*
ctime is depricated due to a thread-safety issue.
That's not a problem here. Compiler warning
recommends converting to ctime_s, which, unfortunately
has a different and awkward interface.
*/
}
void Point4D::updateTime() {
t = std::time(0);
}
void Point4D::show() {
std::cout << "\n " << "Point4D {";
std::cout << "\n "
<< x << ", " << y << ", " << z << ", ";
std::cout << "\n " << timeToString();
std::cout << " }";
}
/* required for showType(T t, const std::string& nm) */
std::ostream& operator<<(
std::ostream& out, Point4D& t1
) {
out << "Point4D {";
out << " " << t1.xCoor() << ", " << t1.yCoor() << ", "
<< t1.zCoor() << std::endl
<< " " << t1.timeToString() << std::endl
<< " }" << std::endl;
return out;
}
1.3 Point4D Demo
/*------------------------------------------------*/
showNote("user-defined type Point4D");
Point4D p1;
p1.show();
p1.xCoor() = 42;
p1.zCoor() = -3.5;
/*- t contains time of construction -*/
p1.show();
print();
print("--- showType(p1, \"p1\", nl) ---");
showType(p1, "p1", nl);
std::cout << " p1.xCoor() returns value "
<< p1.xCoor() << "\n";
showOp("Point4D p2 = p1 : copy construction");
Point4D p2 = p1; // copy construction
p2.show();
showOp("p2.xCoor() *= 2");
p2.xCoor() *= 2;
p2.show();
showOp("p1 = p2 : copy assignment");
p1 = p2; // copy assignment
p1.show();
--------------------------------------------------
user-defined type Point4D
--------------------------------------------------
Point4D {
0, 0, 0,
Mon May 1 16:04:27 2023
}
Point4D {
42, 0, -3.5,
Mon May 1 16:04:27 2023
}
--- showType(p1, "p1", nl) ---
p1 type: class Point4D
size: 32
p1.xCoor() returns value 42
--- Point4D p2 = p1 : copy construction---
Point4D {
42, 0, -3.5,
Thu Dec 7 19:34:59 2023
}
--- p2.xCoor() *= 2 ---
Point4D {
84, 0, -3.5,
Thu Dec 7 19:34:59 2023
}
--- p1 = p2 : copy assignment ---
Point4D {
84, 0, -3.5,
Thu Dec 7 19:34:59 2023
}
1.4 Heap-based operations
1.4.1 Managing std::String instances in the native heap
/*------------------------------------------------*/
showNote("heap-based string instance");
/* standard library type std::string */
/* uses alias pU for std::unique_ptr */
showOp(
"pU<std::string> "
"pStr(new std::string(\"\\\"Road Runner\\\"\")"
);
pU<std::string> pStr(
new std::string("\"Road Runner\"")
);
std::cout << "\n pStr contents = "
<< *pStr << "\n";
showOp("showType(*pStr, \"*pStr\")");
showType(*pStr, "*pStr", nl);
/* std::unique_ptr<T> cannot be copied
but can be moved */
showOp("showType(move(pStr), \"pStr\")");
showType(move(pStr), "pStr", nl);
// showType(pStr, "pStr") is an illegal call
// since pStr cannot be copied to preserve uniqueness
--------------------------------------------------
heap-based string instance
--------------------------------------------------
--- pU<std::string> pStr(new std::string("\"Road Runner\"") ---
pStr contents = "Road Runner"
--- showType(*pStr, "*pStr") ---
*pStr type: class std::basic_string<char,struct std:...
size: 40
--- showType(move(pStr), "pStr") ---
pStr type: class std::unique_ptr<class std::basic_s...
size: 8
1.4.2 Heap-based operations for std::Vector<T>
/*------------------------------------------------*/
/* standard library type std::vector<T> */
showNote("heap-based vector instance");
showOp(
"pU<std::vector<double>>\n "
" pVec(
new std::vector<double>{ 1.5, 2.5, 3.5 }
);"
);
pU<std::vector<double>> pVec(
new std::vector<double>{ 1.5, 2.5, 3.5 }
);
std::cout << "\n *pVec = " << *pVec;
showType(*pVec, "*pVec", nl);
std::cout << "\n pVec = " << pVec;
showType(move(pVec), "move(pVec)", nl);
--------------------------------------------------
heap-based vector instance
--------------------------------------------------
--- pU<std::vector<double>>
pVec(new std::vector<double>{ 1.5, 2.5, 3.5 }); ---
*pVec =
vector<T>: {
1.5, 2.5, 3.5
}
*pVec type: class std::vector<double,class std::allo...
size: 32
pVec = 000002897B682AC0
move(pVec) type: class std::unique_ptr<class std::vector<...
size: 8
1.4.3 Heap-based operations for user-defined Point4D
/*------------------------------------------------*/
/* custom point types */
showNote("heap-based Point4D instance");
showOp("pU<Point4D> pPoint4D(new Point4D())");
pU<Point4D> pPoint4D(new Point4D());
pPoint4D->show();
pPoint4D->xCoor() = 1;
pPoint4D->yCoor() = 2;
pPoint4D->zCoor() = -3;
pPoint4D->updateTime();
pPoint4D->show();
std::cout << "\n pPoint4D->zCoor() = "
<< pPoint4D->zCoor();
showOp("showType(*pPoint4D, \"*pPoint4D\");");
showType(*pPoint4D, "*pPoint4D");
showOp(
"showType(std::move(pPoint4D), \"pPoint4D\");"
);
showType(std::move(pPoint4D), "pPoint4D", nl);
/* pPoint4D moved, so now invalid */
print("\n That's all Folks!\n\n");
}
--------------------------------------------------
heap-based Point4D instance
--------------------------------------------------
--- pU<Point4D> pPoint4D(new Point4D()) ---
Point4D {
0, 0, 0,
Mon May 1 16:04:27 2023
}
Point4D {
1, 2, -3,
Mon May 1 16:04:27 2023
}
pPoint4D->zCoor() = -3
--- showType(*pPoint4D, "*pPoint4D"); ---
*pPoint4D type: class Point4D
size: 32
--- showType(std::move(pPoint4D), "pPoint4D"); ---
pPoint4D type: class std::unique_ptr<class Point4D,stru...
size: 8
That's all Folks!
1.5 AnalysisObj.h - Analysis and Display Functions
/*---------------------------------------------------------
AnalysisObj.h
- Provides functions that analyze types, display results
and other program defined information.
- Some of this code requires complex template operations.
Those will be discussed in the generics bit.
- You can skip the hard parts until then, without loss
of understanding.
*/
#include <typeinfo> // typeid
#include <utility> // move()
#include <sstream> // stringstream
#include <type_traits> // is_scalar, if constexpr
#include <iostream> // cout
#include <vector> // vector
/*---------------------------------------------------------
Display and Analysis function and globals definitions
-----------------------------------------------------------
*/
const std::string nl = "\n";
/*---------------------------------------------------------
Mutable globals are a common source of bugs. We try not
to use them, but will use DisplayParams here to control
how insertion operator sends instances to std output.
Only the trunc variable is used in this demo. The others
will be used in the Bits_GenericCpp demo.
*/
struct displayParams {
size_t left = 2; // number of spaces to indent
size_t width = 7; // width of display row
size_t trunc = 40; // replace text after trunc with ...
} DisplayParams; // global object
/*-----------------------------------------------
Display text after newline and indentation
*/
inline void print(const std::string& txt = "") {
std::cout << "\n " << txt;
}
/*-----------------------------------------------
Display text after newline and indentation
- provides trailing newline
*/
inline void println(const std::string& txt = "") {
std::cout << "\n " << txt << "\n";
}
/*-----------------------------------------------
Overload operator<< required for
showType(std::vector<T> v, const std::vector<T>& nm)
*/
template<typename T>
std::ostream& operator<< (
std::ostream& out, const std::vector<T>& vec
) {
out << "[";
for (auto it = vec.begin(); it != vec.end(); ++it)
{
out << *it;
if (it != vec.end() - 1)
out << ", ";
}
out << "]";
return out;
}
/*-----------------------------------------------
Display emphasized text
*/
inline void showNote(
const std::string& txt, const std::string& suffix = ""
) {
print("--------------------------------------------------");
print(" " + txt);
print("--------------------------------------------------");
std::cout << suffix;
}
/*-----------------------------------------------
Display emphasized line
*/
inline void showOp(
const std::string& opstr, const std::string& suffix = ""
) {
std::cout << "\n --- " << opstr << " ---" << suffix;
}
/*-----------------------------------------------
Helper function for formatting output
- truncates line to N chars and adds ellipsis
*/
inline std::string truncate(size_t N, const char* pStr) {
std::string temp(pStr);
if(temp.length() > N) {
temp.resize(N);
return temp + "...";
}
return temp;
}
/*-----------------------------------------------
Helper function for formatting output
- generates string of n blanks to offset text
*/
inline std::string indent(size_t n) {
return std::string(n, ' ');
}
/*-----------------------------------------------
Display calling name, static class, and size
*/
template<typename T>
void showType(
T t, const std::string &callname,
const std::string& suffix = ""
) {
std::cout << "\n " << callname; // show name at call site
std::cout << " type: "
<< truncate(DisplayParams.trunc,typeid(t).name());
std::cout << "\n size: " << sizeof(t); // show size on stack
std::cout << suffix;
}
/*-----------------------------------------------
Format output for scalar types like primitives
*/
template<typename T>
std::string formatScalar(
const T& t, const std::string& nm,
const std::string& suffix = "",
size_t left = 2
) {
std::stringstream out;
out << "\n" << indent(left) << nm << ": " << t << suffix;
return out.str();
}
/*-----------------------------------------------
Format output for strings
- indent and embed in quotation marks
*/
template<typename T>
std::string formatString(
const T& t, const std::string& nm,
const std::string& suffix = "",
size_t left = 2
) {
std::stringstream out;
out << "\n" << indent(left) << nm
<< ": \"" << t << "\"" << suffix;
return out.str();
}
Analysis and Display Functions:
This code block contains definitions of ten functions, four ofwhich are templates. If this seems too complex for you right
now, you don't have to understand them completely to under-
stand this Bits demo.
You are invited to skim over the definitions to try to see what
they do, not so much how they do it. You can come back later
to pick up any missing details.
The next Bit will explore generics and will help you understand
how these functions work.
Template functions:
Template functions are code generators that create functionoverloads for specific template argument types using a template
pattern.
That creates function overloads for each T argument, e.g.,
several distinct functions generated from a single template
function definition.
⇐ showType(T t, ...)
is the only analysis function in this file. The next Bit will addseveral more.
The other functions simply format and display their instance
arguments, or provide helper activities for display.
1.6 Program Structure
/*-------------------------------------------------------------------
Cpp_Objects.cpp
- depends on Points.h to provide user-defined point class
- depends on Analysis.h for several display and analysis functions
*/
#include <iostream> // std::cout
#include <memory> // std::unique_ptr
#include <vector> // vector<T> class
#include <array> // array<T> class
#include <map> // map<K,V> class
#include <set> // set<T> class
#include "AnalysisObj.h" // Analysis functions for this demo
#include "PointsObj.h" // Point4D class declaration
/*-----------------------------------------------
Note:
Find all Bits code, including this in
https://github.com/JimFawcett/Bits
You can clone the repo from this link.
-----------------------------------------------*/
/*
This demo uses the std::string and std::vector<T> classes
and a user defined class, Point4D, to illustrate how objects
are defined and instantiated.
Operations:
All the classes discussed here provide operations for:
T t2 = t1 // copy construction
T t3 = temporary // move construction
t1 = t2 // copy assignment
t3 = temporary // move assignment
All instances return their resources when they go out of
scope by implicitly calling their destructor.
Primitive types can all be copied.
Most library and user-defined types can be copied, moved,
and deleted by providing member constructors and destructor.
Often compiler generation works well, but for classes with
pointer members developers must provide those methods.
Processing:
All types are static, operations run as native code, and no
garbage collection is needed. Resources are returned at end
of their declaration scope.
*/
void demo_stdlib() { /*-- code elided --*/ }
void demo_Point4D() { /*-- code elided --*/ }
void demo_heap_string() { /*-- code elided --*/ }
void demo_heap_vector() { /*-- code elided --*/ }
void demo_heap_Point4D() { /*-- code elided --*/ }
int main() {
print("Demonstrate C++ Objects\n");
demo_stdlib();
demo_Point4D();
demo_heap_string();
demo_heap_vector();
demo_heap_Point4D();
print("\n That's all Folks!\n\n");
}
Structure:
⇐ Code in the left panel
begins with a series of #include statements that importdeclarations for the standard library and also for two header
only libraries defined in other files in this Bit.
The #include declaratons, shown on in the left panel, are being
superceded by module imports, as of C++20.
For example, all of the standard library includes like
because the build tool I use, CMake, does not support importing
std::libraries.
Modules do work with code compiled in the Visual Studio IDE.
⇐ Execution Flow:
Processing begins on entry to the main function which, in turninvokes several functions, devoted to demonstrating standard
library types and a custom type.
They show, separately, placement of objects in stack memory
then placement in the native heap.
2.0 Build
Build
C:\github\JimFawcett\Bits\Cpp\Cpp_Objects\build > cmake .. -- Building for: Visual Studio 17 2022 -- Selecting Windows SDK version 10.0.22000.0 to target Windows 10.0.22621. -- The C compiler identification is MSVC 19.34.31942.0 -- The CXX compiler identification is MSVC 19.34.31942.0 -- Detecting C compiler ABI info -- Detecting C compiler ABI info - done -- Check for working C compiler: C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.34.31933/bin/Hostx64/x64/cl.exe - skipped -- Detecting C compile features -- Detecting C compile features - done -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Check for working CXX compiler: C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.34.31933/bin/Hostx64/x64/cl.exe - skipped -- Detecting CXX compile features -- Detecting CXX compile features - done -- Configuring done -- Generating done -- Build files have been written to: C:/github/JimFawcett/Bits/Cpp/Cpp_Objects/build C:\github\JimFawcett\Bits\Cpp\Cpp_Objects\build > cmake --build . MSBuild version 17.4.1+9a89d02ff for .NET Framework Checking Build System Building Custom Rule C:/github/JimFawcett/Bits/Cpp/Cpp_Obje cts/CMakeLists.txt Bits_Objects.cpp Cpp_Objects.vcxproj -> C:\github\JimFawcett\Bits\Cpp\Cpp_Ob jects\build\Debug\Cpp_Objects.exe Building Custom Rule C:/github/JimFawcett/Bits/Cpp/Cpp_Obje cts/CMakeLists.txt C:\github\JimFawcett\Bits\Cpp\Cpp_Objects\build >
3.0 Visual Studio Code View
4.0 References
Reference | Description |
---|---|
C++ Story | E-book with thirteen chapters covering most of intermediate C++ |
C++ Bites | Relatively short feature discussions |
STRCode | User-defined string type with all standard methods and functions documented with: purpose, declaration, definition, invocation, and notes. |
w3schools tutorial | Slow and easy walk throught basics. |
cppreference.com | Very complete reference with lots of details and examples. |
Point4d:
something was at that point.
trajectory of an aircraft or a machine tool cutting edge.
⇐ C++ class syntax:
and destruction, all based on class name. They are illustrated
in this block.
destructor, and assignment operator if using code implies a
copy and/or assignment and the class designer has not provided
them. Destructors are always required and are generated if
the class does not provide one.
construction, assignment, and destruction operations for all parts
of the class. These operations are applied member-wise, e.g.,
to each base class and member datum, in order of their declaration.
members and bases have correct copy, destruction, and assignment
semantics.
"default" qualifier to indicate that the designer expects the
generated methods will be correct. Use the "delete" qualifier to
prevent the compiler from generating the deleted operation.
Function overloading:
Essentially a function overload is a new function with a name based
on the original function name and the types of its arguments
created, by a process call "name-mangling".
of the void constructor, Point4D(), and the copy and move
assignment operators are overloads of the operator= function.
⇐ C++ operators
are functions that are called when statements with operator symbolsare encountered, e.g.,
objects of type
process. The overload for Point4D is needed by functions in the
AnalysisObjects.h library.