Why is the copy assignment method not been called? - c++

Am wondering why the copy assignment operator is not been called and yet the assignment seems to happen? This is my code:
#include <cstring>
#include <iostream>
class String{
public:
String(){ std::cout << "Empty Ctor" << '\n'; }
String(const char* s):data{(char*)s}{ std::cout << "Args Ctor"; }
Strin& operator=(const String&);
char* showData(){ return *data; }
private:
char* data;
};
String& String::operator=(const String& s){
std::cout << "Copy Assignment" << '\n'; // <- This is NEVER printed
delete [] data;
data = new char[strlen(s.data)+1];
strcpy(data, s.data);
return *this;
}
int main(){
String* s1 = new String("Hello");
String* s2 = new String("World");
s2 = s1; // This should call the overloaded copy assignment operator!?
std::cout << s2->showData() << '\n'; // prints "Hello"
return 0;
}
The cout in the assignment operator is not been printed
Updating my code - based on earlier suggestions. This works as expected
#include <cstring>
#include <iostream>
class String{
public:
String():data{nullptr}{ std::cout << "Empty ctor" <<'\n' ;}
String(const char* s)
{
std::cout<< "Const Args ctor" <<'\n' ;
data = new char[strlen(s)+1];
strcpy(data, (char *)s);
}
String(const String& other){
std::cout << "Copy Ctor" << '\n';
*this = other;
}
String& operator=(const String& s){
std::cout << "Assignment" << '\n';
if (&s != this){
delete [] data;
data = new char[strlen(s.data)+1];
strcpy(data, s.data);
}
return *this;
}
~String(){
std::cout << "Dtor" << '\n';
delete [] data;
}
char* showData(){ return data; }
private:
char* data;
};
int main()
{
std::cout << "Main" << '\n';
String a("A: Hello");
std::cout << a.showData() <<'\n';
String b("B: World");
std::cout << b.showData() <<'\n';
b = a;
std::cout << b.showData() <<'\n';
return 0;
}

s2 = s1; is just assignment between pointers, it won't call the copy assignment operator, just makes s2 pointing to the same object pointed by s1.
You might want to try with *s2 = *s1;, which uses the copy assignment operator of String. But you don't need the raw pointer indeed, you can just
String s1("Hello");
String s2("World");
s2 = s1; // This would call the overloaded copy assignment operator
std::cout << s2.showData() << '\n'; // prints "Hello"

"s2 = s1; // This should call the overloaded copy assignment operator!?"
No. It should not.
You are just assigning the value of one pointer variable to another. No copy assignment of your objects the pointers point to is happening.
This would do as you expect:
String s1("Hello");
String s2("World");
s1 = s2;
As would
*s1 = *s2;
in your original code.

Ok, lets start with this, pointer is not a string, it is a pointer (that points to a string).
So this:
String* s1 = new String ("BLE");
is creating a string somewhere in memory, and the point that will point to it. So a representation of it is:
+--------+
s -> | 0x005 |----+
+--------+ \
\
\
\
\
+---+---+---+---+---+---+---+
| | | | | B | L | E |
+---+---+---+---+---+---+---+
0x 1 2 3 4 5 6 7
This:
String* s2;
s2 = s1;
makes s2 pointing to same address (holding the same value at assign location for the pointer):
+--------+
s1-> | 0x005 |+
+--------+ \
\
+--------+ \
s2-> | 0x005 |----+
+--------+ \
\
\
+---+---+---+---+---+---+---+
| | | | | B | L | E |
+---+---+---+---+---+---+---+
0x 1 2 3 4 5 6 7
Operator = in String is for type String not for the pointer, so if you have something like this:
String s1 = "BLA";
String s2 = s1;
This creates brand new object and copies data over to that new object.
I suggest you read Programming Principles and Practice Using C++ chapter 18.5.
I also believe you have a lot of errors in your code. You cannot do what you are doing.
Here is sample of your code which actually uses copy assignment:
#include <cstring>
#include <iostream>
class String{
public:
String() : data {new char[1]} { std::cout << "Empty Ctor\n" << '\n'; }
String(const char* s):data{new char[strlen(s)]}{ strcpy (data, s); std::cout << "Args Ctor\n"; }
String& operator=(const String&);
char* showData(){ return data; }
private:
char* data;
};
String& String::operator=(const String& s){
std::cout << "Copy Assignment" << '\n'; // <- This is NEVER printed
delete[] data;
data = new char[strlen(s.data)+1];
strcpy(data, s.data);
return *this;
}
int main(){
String s1 ("Hello");
String s2 ("World");
s2 = s1; // This should call the overloaded copy assignment operator!?
std::cout << s2.showData() << '\n'; // prints "Hello"
return 0;
}
or as other suggested:
#include <cstring>
#include <iostream>
class String{
public:
String() : data {new char[1]} { std::cout << "Empty Ctor\n" << '\n'; }
String(const char* s):data{new char[strlen(s)]}{ strcpy (data, s); std::cout << "Args Ctor\n"; }
String& operator=(const String&);
char* showData(){ return data; }
private:
char* data;
};
String& String::operator=(const String& s){
std::cout << "Copy Assignment" << '\n'; // <- This is NEVER printed
delete[] data;
data = new char[strlen(s.data)+1];
strcpy(data, s.data);
return *this;
}
int main(){
String* s1 = new String("Hello");
String* s2 = new String("World");
*s2 = *s1; // This should call the overloaded copy assignment operator!?
std::cout << s2->showData() << '\n'; // prints "Hello"
return 0;
}
This is reason why you should learn C and pointers before you start messing around with pointers in C++.

Because you are assigning a pointer to another pointer. Do this:
*s2 = *s1;

Related

Segment fault upon calling method of class

I was able to safely call builder, but builder2 exits with a segment fault.
The compiler does not output any warnings.
I would like to know the cause of the segment fault.
This code is a builder pattern to compose html. ul and li are collected with emplace_back and finally str() is called to build the parts and return them as string.
#include <memory>
#include <sstream>
#include <string>
#include <vector>
using namespace std;
struct HtmlBuilder;
struct HtmlElement {
string name;
string text;
vector<HtmlElement> elements;
const size_t indent_size = 2;
HtmlElement() {}
HtmlElement(const string& name, const string& text)
: name(name), text(text) {}
string str(int indent = 0) const {
ostringstream oss;
string i(indent_size * indent, ' ');
oss << i << "<" << name << ">" << endl;
if (text.size() > 0)
oss << string(indent_size * (indent + 1), ' ') << text << endl;
for (const auto& e : elements) oss << e.str(indent + 1);
oss << i << "</" << name << ">" << endl;
return oss.str();
}
static unique_ptr<HtmlBuilder> build(string root_name) {
return make_unique<HtmlBuilder>(root_name);
}
};
struct HtmlBuilder {
HtmlBuilder(string root_name) { root.name = root_name; }
// void to start with
HtmlBuilder& add_child(string child_name, string child_text) {
HtmlElement e{child_name, child_text};
root.elements.emplace_back(e);
return *this;
}
// pointer based
HtmlBuilder* add_child_2(string child_name, string child_text) {
HtmlElement e{child_name, child_text};
root.elements.emplace_back(e);
return this;
}
string str() { return root.str(); }
operator HtmlElement() const { return root; }
HtmlElement root;
};
int main() {
// easier
HtmlBuilder builder{"ul"};
builder.add_child("li", "hello").add_child("li", "world");
cout << builder.str() << endl;
auto* builder2 = HtmlElement::build("ul")
->add_child_2("li", "hello")
->add_child_2("li", "world");
cout << (*builder2).str() << endl;
return 0;
}
The output results are as follows
hello
world
Segmentation fault (core dumped)
You dynamically create a std::unique_ptr object holding the builder with HtmlElement::build("ul"). This std::unique_ptr object gets destroyed at the end of the full expression which means the object builder2 points to is destroyed and dereferencing it is undefined behaviour resulting in the crash you observed.
I recommend not returning a dynamically allocated builder object at all. Instead move the definition of the HtmlElement::build below the definition of HtmlBuilder. You may also want to consider allowing for move semantics to avoid creating unnecessary copies of the objects:
struct HtmlElement {
...
static HtmlBuilder build(string root_name);
};
struct HtmlBuilder {
HtmlBuilder(string root_name) { root.name = std::move(root_name); }
// void to start with
HtmlBuilder& add_child(string child_name, string child_text) &
{
HtmlElement e{ child_name, child_text };
root.elements.emplace_back(e);
return *this;
}
HtmlBuilder&& add_child(string child_name, string child_text) &&
{
HtmlElement e{ child_name, child_text };
root.elements.emplace_back(e);
return std::move(*this);
}
string str() { return root.str(); }
operator HtmlElement() &&
{
return std::move(root);
}
HtmlElement root;
};
inline HtmlBuilder HtmlElement::build(string root_name) {
return { root_name };
}
int main() {
HtmlBuilder builder{ "ul" };
builder.add_child("li", "hello").add_child("li", "world");
cout << builder.str() << endl;
auto builder2 = HtmlElement::build("ul")
.add_child("li", "hello")
.add_child("li", "world");
cout << builder2.str() << endl;
HtmlElement product = std::move(builder2); // use move constructor for creating product here (works without std::move if HtmlElement::build is in the same full expression as the conversion to HtmlElement)
return 0;
}
As far as I understand, you return a unique_ptr with the build method. However, in your main function when you do this:
auto* builder2 = HtmlElement::build ...
What you actually do is retrieve the raw pointer from the unique_ptr that is returned and then you let this temporary unique_ptr be destroyed. Which in turn frees the instance that was handled by the unique_ptr, which is the very same instance that you retrieved the raw pointer for.
So in the next line:
cout << (*builder2).str() << endl;
You are actually trying to dereference a pointer to invalid memory, since the instance that resided there was just deleted by the temoporary unique pointer that was destroyed in the previous line.
If you remove the raw pointer from the "auto" part like this:
auto builder2 = HtmlElement::build("ul") ...
Then you will have a smart pointer to your instance.
Then you can call the str method on the smart pointer as if it was a pointer to your instance:
cout << builder2->str() << endl;

The delete keyword is not working even though when I build the code it say there are not errors

#include <iostream>
#include <cstring>
#include <string>
class Cd {
private:
const char* performers;
const char* label;
int selections;
double playtime;
public:
Cd(){
int eight = 8;
performers = new char[eight];
label = new char[eight];
label = "Unknown";
performers = "Unknown";
selections = 0;
playtime = 0.0;
}
Cd(const char* performers, const char* label, int selections, double playtime) {
this->performers = new char[strlen(performers)+1];
this->performers = performers;
this->label = new char[strlen(label) + 1];
this->label = label;
this->selections = selections;
this->playtime = playtime;
}
void setLabel(const char* label) {
this->label = label;
}
void setPerformers(const char* performers) {
this->performers = performers;
}
void setSelections(int selections) {
this->selections = selections;
}
void setPlaytime(double playtime) {
this->playtime = playtime;
}
const char* getLabel() {
return label;
}
const char* getPerformers() {
return performers;
}
int getSelections() {
return selections;
}
double getPlaytime() {
return playtime;
}
virtual void Report() {
std::cout << "Performers: " << performers<<std::endl
<<"Label: " <<label<< std::endl
<<"Number of selections: " << selections << std::endl
<<"Play time: " << playtime << std::endl;
}
~Cd() {
delete[] performers;
delete[] label;
}
};
class Classic : public Cd {
private:
const char* primaryWork;
public:
Classic() {
primaryWork = new char[8];
primaryWork = "Unknown";
}
Classic(const char* primaryWork, const char* performers, const char* label, int selections, double playtime) {
this->primaryWork = new char[strlen(primaryWork) + 1];
this->primaryWork = primaryWork;
setLabel(label);
setPerformers(performers);
setSelections(selections);
setPlaytime(playtime);
}
virtual void Report() {
std::cout << "Primary work: " << primaryWork << std::endl<<
"Performers: " << getPerformers() << std::endl <<
"Label: " <<getLabel() << std::endl<<
"Number of selections: " << getSelections() << std::endl
<< "Play time: " << getPlaytime() << std::endl;
}
~Classic() {
delete[] primaryWork;
};
};
int main()
{
Cd c1("Beatles", "Capitol", 14, 35.5);
Classic c2 = Classic("Piano Sonata in B flat, Fantasia in C", "Alfred Brendel", "Philips" , 2, 57.17);
Cd* parent = &c1;
std::cout << "\nUsing object directly:\n";
std::cout << "***************************" << std::endl;
c1.Report();
c2.Report();
std::cout << "\nUsing type cd * pointer to objects:\n";
std::cout << "***************************" << std::endl;
// Call Report() using Cd type pointer created above
parent->Report();
Classic* classic = &c2;
classic->Report();
// Call Report() using Cd type pointer containing Classic object address
return 0;
}
I don't understand what is wrong with the delete keyword. My code is supposed to delete the memory allocation for the array at the end to save memory, but it is not working. The delete_scalar file pops up and the code does not finish executing. The rest of the code is working fine. The big problem is when I build the code the compiler tells me that there are no errors found.
Write
std::string performers;
instead of
const char* performers;
and so on for the other string-like members. Then you can drop the new[] and delete[] entirely. The bug you have is a reassignment of the pointer to some read-only const char[] type "Unknown" which compiles due to the rules of pointer decay.
Using bare pointers for class members also means you need to write constructors, a destructor, and an assignment operator correctly. If you use std::string then you can rely on the compiler-generated ones.

C++ different constructor calls

I wrote a little class to learn the different constructor calls
#include <iostream>
#include <cstdlib>
#include <cstring>
class String {
private:
char *str;
public:
explicit String(const char *p);
String(String&& StringObject);
String(String& stringObject);
~String();
friend std::ostream& operator<<(std::ostream& os, const String& s);
friend String operator+(const String& s1, const String& s2);
};
String::String(const char *p)
{
size_t l = strlen(p) + 1;
str = (char *)malloc(sizeof(char) * l);
if (str)
strcpy(str, p);
std::cout << "constructor call" << std::endl;
}
String::String(String& stringObject)
{
str = (char *)malloc(sizeof(char) * (strlen(stringObject.str) + 1));
strcpy(str, stringObject.str);
std::cout << "copy constructor call" << std::endl;
}
String::~String()
{
free(str);
}
String::String(String&& stringObject)
{
this->str = stringObject.str;
stringObject.str = nullptr;
std::cout << "move constructor call" << std::endl;
}
std::ostream& operator<<(std::ostream& os, const String& s)
{
return os << s.str;
}
String operator+(const String& s1, const String& s2)
{
size_t sl1 = strlen(s1.str);
size_t sl2 = strlen(s2.str);
char str[sl1 + sl2 + 1];
strcpy(str, s1.str);
strcpy(str+sl1, s2.str);
return String{str};
}
String doNothing(String obj)
{
std::cout << "output in function: " << obj << std::endl;
return obj;
}
int main()
{
String s1("text");
String s2("and more text");
std::cout << "output: " << s1 << std::endl;
String s3 = doNothing(s1+ String{" "} + s2);
String s4 = doNothing(s1);
String s5 = s1 + s4;
}
The output is
constructor call
constructor call
output: text
constructor call
constructor call
constructor call
output in function: text and more text
move constructor call
copy constructor call
output in function: text
move constructor call
constructor call
I think the constructor calls on line 4 to 6 come from the method call
String s3 = doNothing(s1+ String{" "} + s2);
Why does the method call not cause a call to the copy constructor like the second method call?
String s4 = doNothing(s1);
Maybe because s1 is a lvalue?
Can the move constructor only be called when a function returns an object or also when a reference or pointer to an object is returned?
I'd suppose that you start line counting at 1 ;-)
The case with a complex expression
We would expect the following statement to construct in principe a temporary String for String{" "}, and then a temporary string for each of the two +. Since the parameter obj is to be constructed from a temporary object, the move constructor could be used:
String s3 = doNothing(s1+ String{" "} + s2);
The idea, of the move constructor is to be able to take advantage of the fact that the original object is disposable.
The return value of the function is also a temporary value and this one is used to construct s3 with the move constructor.
But shouldn't we then have 3 constructors and two move constructors? No, because there are copy elision rules. These cause the compiler to avoid unnecessary a copies/moves, and construct directly into the target. This happens here for the parameter.
The case with the lvalue
The next statement constructs the parameter obj with the copy constructor of an lvalue s1:
String s4 = doNothing(s1);
This is less surprising. The value is in principle copied to a temporary return value, which is then used to move-construct s4 from a temporary.
But again, copy elision simplifies this to a copy constructor and a move.
Analysing what happens
You could analyse more in detail what happens, by displaying the address of the object. This is very helpful to understand what happens on what object:
class String {...};
String::String(const char *p)
{
size_t l = strlen(p) + 1;
str = new char[l];
if (str)
strcpy(str, p);
std::cout << "constructor call:" << this<<"="<<str<< std::endl;
}
String::String(const String& stringObject)
{
str = new char[ strlen(stringObject.str) + 1];
strcpy(str, stringObject.str);
std::cout << "copy constructor call:" <<this<<"="<<str<< std::endl;
}
String::~String()
{
delete[] str;
}
String::String(String&& stringObject)
{
this->str = stringObject.str;
stringObject.str = nullptr;
std::cout << "move constructor call:"<<this <<"="<<str<< std::endl;
}
String operator+(const String& s1, const String& s2)
{
size_t sl1 = strlen(s1.str);
size_t sl2 = strlen(s2.str);
char str[sl1 + sl2 + 1];
strcpy(str, s1.str);
strcpy(str+sl1, s2.str);
return String{str};
}
String doNothing(String obj)
{
std::cout << "output in function: " <<&obj<<":"<< obj << std::endl;
return obj;
}
Online demo
Unrelated advice
I highly recommend that you get rid of malloc() and free() in C++ so I used here new[] and delete[] (or new and delete if it's not about arrays). In a second step you could alsoe get rid of the strcpy()

Dynamic Array Error Help: '>>': no operator found which takes a right-hand operand of type 'overloaded-function'

I have been having trouble with the last bit of my code. I declared an instance of Grocery using the parameterized, copy and default constructor and made use of the operator= and operator<< overload. I am now having difficulty trying to create a dynamic array. I have to fill the array with the contents of my text filefileName.txt.
When I run this code I keep getting this one error:
Error C2679 binary '>>': no operator found which takes a right-hand operand of type 'overloaded-function' (or there is no acceptable conversion). Can I not access the setters in the array using >> (w/o overloading)? If not, how can I?
int main()
{
// Parameter - Instance 1
Grocery g1("NA", 0, "NA");
g1.setName("Milk");
g1.setQuant("1");
g1.setUnit("Bottle");
Grocery g2(g1); // Calls copy constructor
// Default constructor - Instance 3
//Grocery g3();
// Operator =
Grocery g4;
cout << "Operator = Running" << endl;
g4 = g2;
cout << g4.getName() << endl;
cout << g4.getQuant() << endl;
cout << g4.getUnit() << endl << endl;
// Operator <<
cout << "Operator<< Running" << endl;
Grocery g5("Salt", "1", "Bag");
cout << g5 << endl;
//cout << g5.getName();
//cout << g5.getQuant();
//cout << g5.getUnit();
// Dynamic Array of Grocery
Grocery* groceryArray;
groceryArray = new Grocery[3];
ifstream inputFile;
inputFile.open("fileName.txt");
for (int i = 0; i < 3; i++)
{
inputFile >> groceryArray[i].setName; // LINE ERROR IS HERE**
}
inputFile.close();
delete[]groceryArray;
return 0;
}
//Grocery.h
#pragma once
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
class Grocery
{
private:
string* m_name;
string* m_quant;
string* m_unit;
public:
Grocery(); // Default constructor
Grocery(string n, string q, string u); // Parametered constructor
~Grocery(); // Destructor
Grocery(const Grocery& rhs); // Copy constructor
Grocery& operator=(const Grocery& rhs); // Operator=
friend ostream& operator<<(ostream& out, const Grocery& rhs); //Operator>>
string getName();
void setName(string n);
string getQuant();
void setQuant(string q);
string getUnit();
void setUnit(string u);
};
#include "Grocery.h"
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
Grocery::Grocery() // Default constructor
{
m_name = new string;
m_quant = new string;
m_unit = new string;
*m_name = "N/A";
*m_quant = "NA";
*m_unit = "N/A";
}
Grocery::Grocery(string n, string q, string u) // Parameterized constructor
{
//cout << "3 parameter constructor called" << endl;
// Initializes member variables as parameter variables
m_name = new string;
m_quant = new string;
m_unit = new string;
*m_name = n;
*m_quant = q;
*m_unit = u;
}
Grocery::Grocery(const Grocery& rhs) // Copy
{
m_name = new string;
m_quant = new string;
m_unit = new string;
*m_name = *rhs.m_name;
*m_quant = *rhs.m_quant;
*m_unit = *rhs.m_unit;
}
Grocery& Grocery::operator=(const Grocery& rhs) // Operator=
{
// Performs deep copy of other Grocery instance
if (this == &rhs)
return *this;
*m_name = *rhs.m_name;
*m_quant = *rhs.m_quant;
*m_unit = *rhs.m_unit;
return *this;
}
ostream& operator<<(ostream& out, const Grocery& rhs) // Operator<<
{
out << *rhs.m_name << endl << *rhs.m_quant << endl << *rhs.m_unit << endl;
return out;
}
Grocery::~Grocery() // Destructor
{
delete m_name;
delete m_quant;
delete m_unit;
*m_name = nullptr;
*m_quant = nullptr;
*m_unit = nullptr;
}
string Grocery::getName() { return *m_name; }
void Grocery::setName(string n) { *m_name = n; }
string Grocery::getQuant() { return *m_quant; }
void Grocery::setQuant(string q) { *m_quant = q; }
string Grocery::getUnit() { return *m_unit; }
void Grocery::setUnit(string u) { *m_unit = u; }
First things first: groceryArray[i].setName is a method of Grocery which makes the statement **inputFile >> groceryArray[i].setName; highly nonsensical.
What you want instead is to first read in a string and then change the name like so:
std::string name;
**inputFile >> name;
groceryArray[i].setName(name);

C++ copy constructor is not invoking (compilation error)

This code doesn't compile (please ignore passing by value in operator+, I could replace it by reference & but it still doesn't solve the issue)
I expect in main function:
String s3 = s + s2; // COMPILATION ERROR
to be compiled ok (because I declare copy constructor) but it gives error ("no matching constructor")
#include <iostream>
#include <string>
class String {
public:
String()
{
std::cout << "Constructor " << this << std::endl;
data = new char[100];
};
String(char * str) : String()
{
std::cout << "Char * Constructor " << this << std::endl;
strcpy(this->data,str);
};
String(String & rhs) : String() {
std::cout << "Copy Constructor " << this << std::endl;
strcpy(data, rhs.data);
};
void print() {
printf("%s\n",data);
}
~String() {
std::cout << "Destructor " << this << std::endl;
if (data) {
delete data;
data = nullptr;
}
};
friend String operator+(String s1, String s2);
private:
char * data;
};
String operator+(String s1, String s2)
{
String temp;
delete [] temp.data;
temp.data =
new char[strlen(s1.data) + strlen(s2.data) + 1];
strcpy(temp.data, s1.data);
strcat(temp.data, s2.data);
return temp;
}
int main(int argc, const char * argv[])
{
String s("herer");
s.print();
String s2 = s;
s2.print();
String s3 = s + s2; // COMPILATION ERROR
return 0;
}
There are several errors with your code. First
String(char * str)
Needs to be
String(const char * str)
If you want to use string literals like you do in main() with String s("herer");. Secondly Your copy constructor
String(String & rhs)
Needs to take be
String(const String & rhs)
So that it can bind to temporaries.
Third you need to include <cstring> or <string.h> in order to use strcpy().
Lastly you are using the wrong delete in you destructor. When you call new you use delete and when you use new[] you call delete []. You use new[] to allocate data but you are deleting it with delete.
You can see a working example of your code here
The argument of the copy constructor must be const&.
String(String const & rhs) : String() { ... }