Every example that I've seen which implement the Rule of Five, implements the rule on a class without inheritance and polymorphism (virtual functions).
How can the rule of 5 be applied to the sample code bellow?
The test example uses c-arrays for dynamic objects. This is slightly contrived of course in order to have dynamic objects to manage to demonstrate the Rule of Five. In practice, it could have been anything (FILE ptrs, database handles, etc). Therefore, don't suggest using vector, or turning the exercise into demonstration of the Rule of Zero.
I'd prefer the solution not to use the copy-swap idiom because I find the non-copy-swap method more explicit in the case of non-inheriting classes. But if one wished to illustrate both methods, that would also be great.
Notes about sample code:
B inherits from A.
Both A & B have their own dynamic object to manage (a contrived c-array).
Class T is a contrived class to have custom type for diagnostic prints. The c-arrays store T elements.
Class A & B are void of Rule of Five implementation (to be defined by answer)
The c-arrays have not yet been allocated in the sample code, but of course, they would be at construction based on respective bufferSize.
Sample Code
//TEST TYPE
class T
{
public:
T() { cout << "ctorT" << endl; }
~T() { cout << "dtorT" < endl; }
T(const T& src) { cout << "copy-ctorT " << endl; }
T& operator=(const T& rhs) { cout << "copy-assignT" << endl; return *this; }
T(T&& src) noexcept { cout << "move-ctorT " << endl; }
T& operator=(T&& rhs) { cout << "move-assignT" << endl; return *this; }
}
class A
{
string nameParent = "A";
size_t bufferSizeParent = 0;
T *pBufferParent = nullptr; //c-array
public:
A(string tNameParent, size_t tBufferSizeParent) : nameParent(tNameParent), bufferSizeParent(tBufferSizeParent)
{
cout << "ctorA " << nameParent << endl;
}
virtual ~A(){ cout << "dtorA " << nameParent << endl; }
virtual string getName() { return nameParent; }
virtual void setName(const string name) { nameParent = name; }
};
class B : public A
{
string nameChild = "B";
size_t bufferSizeChild = 0;
T *pBufferChild = nullptr; //c-array
public:
B(string tNameChild, string tNameParent, size_t tBufferSizeChild, size_t tBufferSizeParent)
: A(tNameParent, tBufferSizeParent), nameChild(tNameChild), bufferSizeChild(tBufferSizeChild)
{
cout << "ctorB " << nameChild << endl;
}
~B(){ cout << "dtorB " << nameChild << endl; }
virtual string getName() override final { return nameChild; }
virtual void setName(const string name) override final { nameChild = name; }
};
A good rule of thumb is that a class should directly manage at most one resource. Other classes have all defaulted special members, preferably implicitly, but explicitly if they are to be declared in some variation.
Your example becomes
struct T;
extern T acquire_T();
extern void release_T(T);
class THandle
{
T handle;
public:
THandle()
: handle(acquire_T()) {}
~THandle() { release_T(handle); }
THandle(const THandle &) = delete;
THandle(THandle && other)
: handle(other.handle)
{ other.handle = {}; }
THandle & operator=(const THandle &) = delete;
THandle & operator=(THandle && other)
{ std::swap(handle, other.handle); }
}
class A
{
std::string nameParent = "A";
std::size_t bufferSizeParent = 0;
THandle tParent;
public:
A() {}
A(std::string tNameParent, std::size_t tBufferSizeParent) : nameParent(tNameParent), bufferSizeParent(tBufferSizeParent)
{ }
virtual ~A() = default;
A(const A &) = default;
A(A &&) = default;
A & operator=(const A &) = default;
A & operator=(A &&) = default;
virtual string getName() { return nameParent; }
virtual void setName(const string name) { nameParent = name; }
};
class B : public A
{
std::string nameChild = "B";
std::size_t bufferSizeChild = 0;
THandle tChild;
public:
B() {}
B(string tNameChild, string tNameParent, size_t tBufferSizeChild, size_t tBufferSizeParent)
: A(tNameParent, tBufferSizeParent), nameChild(tNameChild), bufferSizeChild(tBufferSizeChild), bufferChild(std::make_unique(tBufferSizeChild))
{ }
virtual string getName() override final { return nameChild; }
virtual void setName(const string name) override final { nameChild = name; }
};
Related
I have a parent DataType class from which I inherit Data Type Int, DataType Double, DataTypeEnum
and CDataTypeStruct. Somewhere I use the print () method defined by the parent and somewhere I rewrite it. I call the print method using the << operator.
Why, when I call a title for the CDataTypeEnum type, everything is displayed correctly, as I have the print defined in the CDaraTypeEnum.
I get this
struct {
int int enum}
but if I want to list cout << structure << endl; so for the CDataTypeStruct type, I don't get an overloaded print method for each object?
Just to make the statement look like this
struct {
int int
enum {
NEW,
FIXED,
BROKEN,
DEAD
}
}
--
All program https://onecompiler.com/cpp/3y2rhbm7a and here's a minimal reproducible example:
#include <iostream>
#include <list>
#include <memory>
#include <set>
#include <string>
#include <unordered_set>
#include <vector>
using namespace std;
class CDataType
{
public:
CDataType(string type, size_t size);
friend ostream& operator << (ostream &os, CDataType &x);
virtual ostream& print (ostream &os) const;
protected:
string m_Type;
size_t m_Size;
};
CDataType::CDataType(string type, size_t size)
: m_Type(type),
m_Size(size)
{
}
ostream& operator << (ostream &os, CDataType &x)
{
x.print(os);
return os;
}
ostream& CDataType::print (ostream &os) const
{
os << m_Type;
return os;
}
class CDataTypeInt : public CDataType
{
public:
CDataTypeInt();
};
CDataTypeInt::CDataTypeInt()
: CDataType("int", 4)
{
}
class CDataTypeEnum : public CDataType
{
public:
CDataTypeEnum();
CDataTypeEnum& add(string x);
virtual ostream& print (ostream &os) const;
protected:
vector<string> listEnums;
set<string> listEnumsNames;
};
CDataTypeEnum::CDataTypeEnum()
: CDataType("enum", 4)
{
}
ostream& CDataTypeEnum::print(ostream &os) const
{
os << m_Type << "{\n";
for (auto i=listEnums.begin(); i != listEnums.end(); ++i )
{
os << *i;
if(i != listEnums.end()-1)
{
os << ",";
}
os << "\n";
}
os << "}";
return os;
}
CDataTypeEnum& CDataTypeEnum::add(string x)
{
if(listEnumsNames.find(x) == listEnumsNames.end())
{
listEnums.push_back(x);
listEnumsNames.emplace(x);
}
else
cout << "vyjimkaa" << endl;
// CSyntaxException e("Duplicate enum value: " + x);
return *this;
}
class CDataTypeStruct : public CDataType
{
public:
virtual ostream& print (ostream &os) const;
CDataTypeStruct();
CDataTypeStruct& addField(const string &name, const CDataType &type);
protected:
list<unique_ptr<CDataType>> m_Field;
unordered_set<string> m_Field_names;
};
CDataTypeStruct::CDataTypeStruct()
:CDataType("struct", 0)
{
}
CDataTypeStruct& CDataTypeStruct::addField(const string &name, const CDataType &type)
{
if( m_Field_names.find(name) == m_Field_names.end() )
{
m_Field.push_back(make_unique<CDataType>(type));
m_Field_names.emplace(name);
}
// else
//throw CSyntaxException("Duplicate field: " + name);
return *this;
}
ostream& CDataTypeStruct::print (ostream &os) const
{
os << m_Type << "{\n";
for(const auto &uptr : m_Field)
{
uptr->print(os) << " " /*<< "{\n"*/;
}
os << "}";
return os;
}
int main()
{
CDataTypeInt inta;
CDataTypeInt intb;
CDataTypeStruct struktura;
CDataTypeEnum enumos;
enumos.add( "NEW" ).add ( "FIXED" ) .add ( "BROKEN" ) .add ( "DEAD" );
struktura.addField("integera", inta);
struktura.addField("integerb", intb);
struktura.addField("bbb", enumos);
cout << enumos << endl;
cout << struktura << endl;
}```
As I already suggested in a similar question you posted already (and deleted), you're again slicing here:
struktura.addField("integera", inta); // Slicing
struktura.addField("integerb", intb); // Slicing
struktura.addField("bbb", enumos); // Slicing
Again, consider following guideline "A polymorphic class should suppress public copy/move" from the C++ Core Guidelines. To do this:
class CDataType {
public:
// ...
// Disable move and copy
CDataType(CDataType const &) = delete;
CDataType(CDataType &&) = delete;
CDataType& operaror=(CDataType const &) = delete;
CDataType& operaror=(CDataType &&) = delete;
// Dtor should be virtual.
virtual ~CDataType() = default;
// ...
};
Then, adapt your code accordingly (as it won't compile anymore).
Also, The destructor of CDataType should be virtual.
Edit: Please consider the following example, which hopeful makes the slicing issue clearer:
#include <cstdio>
#include <list>
#include <memory>
struct A {
A() = default;
A(A const&) { std::puts("A(A const&)"); }
virtual ~A() { std::puts("~A()"); }
};
struct B : public A {
B() = default;
B(B const&) { std::puts("B(B const&)"); }
~B() override { std::puts("~B()"); }
};
void slicing_addField(A const& a) {
std::puts("slicing_f");
std::list<std::unique_ptr<A>> l;
l.push_back(std::make_unique<A>(a));
}
void non_slicing_addField(std::unique_ptr<A> a) {
std::puts("non_slicing_f");
std::list<std::unique_ptr<A>> l;
l.push_back(std::move(a));
}
int main() {
// This is what you do
slicing_addField(B{});
// This is how you may solve
non_slicing_addField(std::make_unique<B>());
}
Output:
slicing_f
A(A const&)
~A()
~B()
~A()
non_slicing_f
~B()
~A()
As you can see from the output, you're only calling A(A const&) in slicing_addField.
This means the call to make_unique<A>(a) is allocating an object of run-time type A (while you want it of run-time type B).
The main problem is that you try to copy the CDataType decendant in addField by using make_unique<CDataType>, but that actually creates a CDataType, not a CDataTypeInt, CDataTypeEnum or CDataTypeStruct.
Since constructors can't be virtual (in standard C++) you need to create a separate virtual function to do the copying. A common name for such a function is clone. Example:
class CDataType {
public:
CDataType(string type, size_t size);
CDataType(const CDataType&) = delete;
CDataType& operator=(const CDataType&) = delete;
virtual ~CDataType() = default; // add virtual dtor
virtual std::unique_ptr<CDataType> clone() const {
return std::make_unique<CDataType>(m_Type, m_Size);
}
// ...
};
class CDataTypeEnum : public CDataType {
public:
using CDataType::CDataType;
std::unique_ptr<CDataType> clone() const override {
// use make_unique to create the correct type:
auto np = std::make_unique<CDataTypeEnum>(m_Type, m_Size);
np->listEnums = listEnums;
np->listEnumsNames = listEnumsNames;
return np;
}
// ...
};
class CDataTypeStruct : public CDataType {
public:
using CDataType::CDataType;
std::unique_ptr<CDataType> clone() const override {
// use make_unique to create the correct type:
auto np = std::make_unique<CDataTypeStruct>(m_Type, m_Size);
np->m_Field_names = m_Field_names;
for(auto& cdtp : m_Field)
np->m_Field.emplace_back(cdtp->clone()); // use clone here
return np;
}
// ...
};
Then addField would look like this:
CDataTypeStruct& CDataTypeStruct::addField(const string& name,
const CDataType& type) {
if (m_Field_names.find(name) == m_Field_names.end()) {
m_Field.emplace_back(type.clone()); // and use clone here
m_Field_names.emplace(name);
}
return *this;
}
I need to implement one abstract class, three its concrete subclasses, class which goal to create one of this three classes instances and last class executor of three classes. Requirements are c++98, and not to use if/elseif/else to construct class instance, like i did in a Maker class method make Form. What mechanism i need to avoid if / elseif / else?
For example:
test.h
#ifndef TEST_H
#define TEST_H
#include <iostream>
class Executor {
private:
const std::string name;
public:
Executor(const std::string &name = "") {};
const std::string getname() const {return name;}
};
class BForm {
private:
const std::string _name;
public:
BForm(const std::string &name = "") : _name(name) {};
virtual ~BForm() {};
virtual void execute(const Executor &src) = 0;
const std::string getname() {return _name;}
virtual const std::string gettarget() = 0;
};
class Form1 : public BForm{
private:
std::string _target;
public:
Form1(const std::string &target = "") : BForm("form1"), _target(target) {};
virtual ~Form1() {};
virtual void execute(const Executor &src) {
std::cout << src.getname() << " exec form1 target:" << _target << std::endl;
}
virtual const std::string gettarget() {return _target;}
};
class Form2 : public BForm {
private:
std::string _target;
public:
Form2(const std::string &target = "") : BForm("form2"), _target(target) {};
virtual ~Form2() {};
virtual void execute(const Executor &src) {
std::cout << src.getname() << " exec form2 target:" << _target << std::endl;
};
virtual const std::string gettarget() {return _target;}
};
class Form3 : public BForm {
private:
std::string _target;
public:
Form3(const std::string &target = "") : BForm("form3"), _target(target) {};
virtual ~Form3() {};
virtual void execute(const Executor &src) {
std::cout << src.getname() << " exec form3 target:" << _target << std::endl;
};
virtual const std::string gettarget() {return _target;}
};
class Maker {
public:
BForm *makeForm(const std::string &name, const std::string &target)
{
/* need to avoid */
if (name == "form1")
return new Form1(target);
else if (name == "form2")
return new Form2(target);
else
return new Form3(target);
}
};
#endif
main.cpp
#include "test.h"
int main() {
Maker maker;
BForm *form;
Executor exec("executor");
form = maker.makeForm("form1", "A");
std::cout << form->getname() << " " << form->gettarget() << std::endl;
form->execute(exec);
delete form;
return (0);
}
You could typedef a pointer to function and then use a map from string to this type (pointer to function). And then use your parameter with indexer syntax to access the correct pointer to function.
Here is an example:
#include <iostream>
#include <map>
// The class definitions with a virtual function hello() common to all
class Base { public: virtual void hello() = 0; };
class Derived1 : public Base { public: void hello() { std::cout << "Derived1"; } };
class Derived2 : public Base { public: void hello() { std::cout << "Derived2"; } };
// The object making functions
Base* Maker1() { return new Derived1; }
Base* Maker2() { return new Derived2; }
int main()
{
// In C++98, without auto, it's worthwhile to typedef complicated types.
// The first one is a function type returning a pointer to Base...
typedef Base* MakerT();
// ... the second one is a map type projecting strings to such function pointers
typedef std::map<std::string, MakerT*> StrToMakerT;
/// The actual map projecting strings to maker function pointers
StrToMakerT strToMaker;
// Fill the map
strToMaker["D1"] = &Maker1;
strToMaker["D2"] = &Maker2;
// user input
std::string choice;
// as long as output works, input works, and the user didn't say "Q":
while (std::cout << "Please input 'D1' or 'D2' or 'Q' for quit: "
&& std::cin >> choice
&& choice != "Q")
{
// Prevent adding new entries to the map foir unknown strings
if (strToMaker.find(choice) != strToMaker.end())
{
// Simply look the function up again, the iterator type is too
// cumbersome to write in C++98
Base* b = (*strToMaker[choice])();
b->hello();
std::cout << '\n';
delete b;
}
else
{
std::cout << "Didn't find your choice, try again.\n";
}
}
std::cout << "Thank you, good bye\n";
}
I have a base class Animal and a derived class Bird : Animal. I use a template class that will store vectors of pointers to either Animal or Bird objects. I want to overload the += operator in such a way that I can insert a new animal right in the Atlas, so m_length = m_length + 1, pages.push_back(animal), just to get the idea.
Here's my template class:
template <class T>
class Atlas2 {
public:
int m_length;
std::list<T> pages;
Atlas2() { m_length = 0; }
~Atlas2() {}
void adauga(T data);
T operator+=(const T& data) {
this->m_length++;
this->pages.push_back(data);
return *this;
};
};
And here's the Animal/Bird classes:
class Animal {
protected:
std::string m_name;
public:
Animal() {}
Animal(std::string name) : m_name{name} {}
virtual void set_name(std::string name) { m_name = name; }
virtual std::string get_name() { return m_name; }
virtual std::string regn() const { return "???"; }
virtual ~Animal() { cout << "Destructor animal" << '\n'; }
};
class Bird : public Animal {
public:
bird() : animal() {}
bird(std::string name) : Animal{name} {}
void set_name(std::string nume) { m_name = nume; }
std::string get_name() { return m_name; }
std::string regn() const override { return "pasare"; }
~bird() { cout << "destructor pasare" << '\n'; }
};
However, I can't figure this out. When I use the overloaded += operator in main() like this:
Pasare *c = new Pasare{"vulture"};
Atlas2<Animal *> Atlas;
Atlas += c;
It shows me an error, that it couldn't convert Atlas<Animal *> to <Animal*>.
How should I implement this correctly? Any tip?
Note: The template works fine, I can store in my list pointers to either Animal or Birds without problems, and access their specific methods. I just can't figure out the += part.
You should return Atlas2<T> & not T:
Atlas2<T>& operator+=(const T& data) {
this->m_length++;
this->pagini.push_back(data);
return *this;
};
The basic problem is that you've declared your operator+= as returning a T, but the return statement in it is return *this;, which is an Atlas2<T>.
If you change the return type to Atlas2<T> &, it should work. That's what you would normally want to return from an operator+= anyways, though with your use, it doesn't matter much as you're ignoring the returned value.
This code compiles and runs fine:
#include <iostream>
class Base
{
public:
Base(int value)
: clean_(true)
{
value_ = new int;
*value_ = value;
}
~Base()
{
if(clean_)
delete value_;
}
Base(Base&& other) noexcept
: value_{std::move(other.value_)},
clean_(true)
{
other.clean_=false;
}
Base& operator=(Base&& other) noexcept
{
value_ = std::move(other.value_);
other.clean_=false;
clean_=true;
}
void print()
{
std::cout << value_ << " : " << *value_ << std::endl;
}
int* value_;
bool clean_;
};
class A : public Base
{
public:
A(int v1, double v2) : Base(v1)
{
a_ = new double;
*a_ = v2;
}
A(A&& other) noexcept
: Base(std::forward<Base>(other)),
a_(std::move(other.a_))
{}
A& operator=(A&& other) noexcept
{
// should not the move assignment operator
// of Base be called instead ?
// If so: how ?
this->value_ = std::move(other.value_);
other.clean_=false;
this->clean_=true;
a_ = std::move(other.a_);
}
void print()
{
std::cout << this->value_ << " "
<< *(this->value_) << " "
<< a_ << " " << *a_ << std::endl;
}
double* a_;
bool clean_;
};
A create_a(int v1,double v2)
{
A a(v1,v2);
return a;
}
int main()
{
Base b1(20);
b1.print();
Base b2 = std::move(b1);
b2.print();
A a1(10,50.2);
a1.print();
A a2 = std::move(a1);
a2.print();
A a3 = create_a(1,2);
a3.print();
}
A is a subclass of Base.
The code of the move assignment operator of A replicates the one of Base.
Is there a way to avoid this replication of code ?
Change int* value_; to int value_; and double* a_; to double a_; and you no longer need to write any of the special member functions as the compiler provided defaults Just Workâ˘
If you really need dynamic memory allocation, then use a RAII type like std::vector, std::unique_ptr, std::shared_ptr, ect. in its place since they are designed to be copied and or moved correctly.
I was expecting the assignment operator to be dynamically bound depending on the argument type:
struct NumberObject;
struct NumberString;
struct DataType
{
virtual void operator=(DataType& rhs) {};
virtual void operator=(NumberString& rhs) {};
};
struct NumberObject : DataType
{
double data = 7;
void operator=(DataType& rhs) override
{
std::cout << "Copying from DataType\n";
// The assignment operator that's called is this one. I was hoping it would be the one below.
}
void operator=(NumberString& rhs) override
{
std::cout << "Copying from NumberString\n";
}
};
struct NumberString : DataType
{
std::string data = "7";
void operator=(NumberString& rhs) override{};
};
int main()
{
DataType *pDataType1, *pDataType2;
pDataType1 = new NumberObject;
pDataType2 = new NumberString;
*pDataType1 = *pDataType2; // Both pointers are to DataType, I was hoping that the assignment operator taking
// NumberString as the right hand side would be called
return 0;
}
From this I guess that C++ can't dynamically bind functions based on the argument type. The dynamic binding only happens when calling a function directly through an object. So I thought I could do this, but it does seem like a roundabout way:
struct NumberObject : DataType
{
double data = 7;
void operator=(DataType& rhs) override
{
std::cout << "Copying from DataType\n";
rhs.assignTo(*this);
}
void operator=(NumberString& rhs) override
{
std::cout << "Copying from NumberString\n";
}
};
struct NumberString : DataType
{
std::string data = "7";
void assignTo(NumberObject& lhs) override
{// Provided the base class had this
lhs.data = atof(this->data.c_str());
}
void operator=(NumberString& rhs) override{};
};
This seems like a hack, and seeing as though the language is capable of dynamic binding, isn't this something it could do easily? Is it a weakness of the language? Or does nobody care about it?
With double dispatching, it would be:
struct NumberObject;
struct NumberString;
struct DataType
{
virtual ~DataType() = default;
virtual void operator=(const DataType&) = 0;
virtual void assignTo(NumberObject&) const = 0;
virtual void assignTo(NumberString&) const = 0;
};
struct NumberObject : DataType
{
double data = 7;
void operator=(const DataType& rhs) override { rhs.assignTo(*this); }
void assignTo(NumberObject& rhs) const override
{
std::cout << "Copying from NumberObject\n";
}
void assignTo(NumberString& rhs) const override
{
std::cout << "Copying from NumberObject\n";
}
};
struct NumberString : DataType
{
std::string data = "7";
void operator=(const DataType& rhs) override { rhs.assignTo(*this); }
void assignTo(NumberObject& rhs) const override
{
std::cout << "Copying from NumberString\n";
}
void assignTo(NumberString& rhs) const override
{
std::cout << "Copying from NumberString\n";
}
};
Demo