Direct initialization of a pointer - c++

I read some sample code in the book C++ Primer:
I do not understand how the direct initialization of a pointer works in ps(new std::string(s)) and ps(new std::string(*p.ps)).
If ps is a pointer to string, then why is it ok to place a string as the parameter inside of its direct initialization constructor?
class HasPtr {
public:
HasPtr(const std::string &s = std::string()):
ps(new std::string(s)), i(0) { }
// each HasPtr has its own copy of the string to which ps points
HasPtr(const HasPtr &p):
ps(new std::string(*p.ps)), i(p.i) { }
HasPtr& operator=(const HasPtr &);
~HasPtr() { delete ps; }
private:
std::string *ps;
int i;
};

The new expression creates space for an std::string on the heap and returns a pointer to it. Hence, it is not a string that ps is initialized with, but a pointer to string, which ps is a type of.

HasPtr::ps is a std::string*.
If I dereference a std::string* like this *ps I get the actual std::string object.
If I have an object const HasPtr p I can get it's member pointer to std::string with: p.ps. This is only possible inside class HasPtr because ps is private.
If I do *p.ps I will get the std::string pointed to by p's member std::string*.
So the code:
HasPtr(const HasPtr &p):
ps(new std::string(*p.ps)), i(p.i) {}
Is using the std::string copy constructor to initialize the dynamically created std::string that will be assigned to this->ps by the HasPtr copy constructor.

Related

Copy constructor and const

Here is the code i'm having problem with
class MyString {
char* str;
public:
MyString(const char* _str) : str(_str) {}
}
This is the error message from the compiler
error: invalid conversion from ‘const char*’ to ‘char*’ [-fpermissive]
Is this because str is char* and _str is const char*?
Should the copy( I mean str(_str) ) be the exact same type (REGARDING const)?
How could I modify this code into reasonable code? I want to keep MyString(const char* _str) as const char*.
Short answer: yes.
Longs answer:
const char* str is a pointer to a region in memory that contains a c string. By marking it const you are saying that it may not be changed. You are attempting to create a new pointer to the same memory region, but this new pointer says that region may be changed, which the compiler complains about (since the original says that this is not allowed).
Note that you are currently not copying the contents to the string (which I suspect you want), but the pointer to the string. To actually copy the string you must first allocate memory and then copy the contents. Or just use a std::string instead, which manages all this for you.
Yes, you cannot assign a const char* pointer (a pointer-to-const) to a char* pointer (a pointer-to-non-const), they are not compatible.
If you don’t mind your class pointing to the original character data as-is, then you can make the str member be a pointer-to-const:
class MyString {
const char* str;
public:
MyString(const char* _str) : str(_str) {}
};
You will just have to make sure the original character data outlives your MyString object.
This is likely not what you want. The other option is to make your class copy the character data into itself:
class MyString {
char* str = nullptr;
public:
MyString(const char* _str) {
if (_str) {
str = new char[strlen(_str) + 1];
strcpy(str, _str);
}
}
...
};
Just be sure to follow the Rule of 3/5/0 to manage that copy correctly. That means implementing a destructor to free the copy, and a copy constructor and copy assignment operator to make copies of the data, and a move constructor and move assignment operator to move the data around:
class MyString {
char* str = nullptr;
public:
MyString() = default;
MyString(const char* _str) {
if (_str) {
str = new char[strlen(_str) + 1];
strcpy(str, _str);
}
}
MyString(const MyString &src) : MyString(src.str) { }
MyString(MyString &&src) {
str = src.str;
src.str = nullptr;
}
~MyString() {
delete[] str;
}
MyString& operator=(MyString rhs) {
MyString tmp(std::move(rhs));
std::swap(src, tmp.src);
return *this;
}
};
A better solution is to use std::string instead, which handles all of these details for you.

What does this constructor do?

Can you explain these lines?
class HasPtr {
public:
HasPtr(const std::string &s = std::string())://this line
ps(new std::string(s)), i(0) { } //and this
HasPtr(const HasPtr &p):
ps(new std::string(*p.ps)), i(p.i) { }
HasPtr& operator=(const HasPtr &);
~HasPtr() { delete ps; }
private:
std::string *ps;
int i;
};
This topic in book is about classes that act like values.
In this declaration of a constructor
HasPtr(const std::string &s = std::string())://this line
ps(new std::string(s)), i(0) { }
there is used the default argument std::string() and the mem-initializer list
ps(new std::string(s)), i(0)
that is executed before the control will be passed to the constructor body. As there is nothing to do in the body the body of the constructor is empty.
So you can call the constructor without an argument like
HasPtr obj;
in this case an empty string created like string() will be used as an argument.
const std::string &s is a reference to a const instance of std::string.
= std::string() is a default value. xyz() is a syntax for a value-initialized instance of xyz.
So when HasPtr is instantiated without an argument (e.g. HasPtr()), the HasPtr(s) constructor will be invoked with s bound to a temporary blank std::string instance (which lives until the end of the full-expression, usually the first ;).
Then ps will be initialized with new std::string(s), making a copy of s on the heap and storing a pointer to it in ps.
If HasPtr is invoked with an actual argument, the = std::string() part will not execute.

C++ unique_ptr initialization

I have question. Let's have this code:
#include <iostream>
#include <string>
#include <memory>
class Writable {
public:
virtual ~Writable() = default;
virtual void write(std::ostream& out) const = 0;
};
class String : public Writable {
public:
String(const std::string& str) : m_str(str) {}
virtual ~String() override = default;
virtual void write(std::ostream& out) const override { out << m_str << '\n'; }
private:
std::string m_str;
};
class Number : public Writable {
public:
Number(double num) : m_num(num) {}
virtual ~Number() override = default;
virtual void write(std::ostream& out) const override { out << m_num << '\n'; }
private:
double m_num;
};
int main() {
std::unique_ptr<Writable> str1(new String("abc"));
std::unique_ptr<Writable> num1(new Number(456));
str1->write(std::cout);
num1->write(std::cout);
}
I dont understand why unique_pointers are defined like this:
std::unique_ptr<Writable> str1(new String("abc"));
Its some kind of shorthand? Or I must do it this way? Is there some kind of equivalent? For example like:
std::unique_ptr<Writable> str1 = std::unique_ptr<String>(new String("abc"));
Here you are creating a new unique_ptr and initializing with a raw pointer returned by new operator.
std::unique_ptr<Writable> str1(new String("abc"));
Here you are creating a new unique_ptr and initializing with a raw pointer returned by new operator and move constructing another unique_ptr with it.
std::unique_ptr<Writable> str1 = std::unique_ptr<String>(new String("abc"));
However compiler can (most likely) perform move elison and make the above two equivalent.
The right way to initialize from c++14 and later is below
std::unique_ptr<Writable> v1 = std::make_unique<String>();
The form of initialization in
std::unique_ptr<Writable> str1(new String("abc"));
is called direct initialization. If the constructor is marked explicit (like in explicit unique_ptr(pointer p) noexcept constructor), this form of initialization is required.
explicit constructor disables copy initialization in the form of std::unique_ptr<Writable> str1 = new String("abc");.
Just like you can write
std::vector<int> foo(10);
to create a vector of 10 int's
std::unique_ptr<Writable> str1(new String("abc"))
creates a std::unique_ptr<Writable> that points to a new String("abc"). It is the same as doing
std::vector<int> foo = std::vector(10);
and
std::unique_ptr<Writable> str1 = std::unique_ptr<String>(new String("abc"));
except that the later cases use copy initialization which can be different in some cases.
To save on some typing you could use
auto str1 = std::make_unique<String>("abc");
instead when you declare your unique_ptr's

C++ - calling to constructor on exist object

I saw some code that reconsruct Object on c++.
from GeeksForGeeks :
#include<iostream>
#include<string.h>
using namespace std;
class String
{
char *p;
int len;
public:
String(const char *a);
};
String::String(const char *a)
{
int length = strlen(a);
p = new char[length +1];
strcpy(p, a);
cout << "Constructor Called " << endl;
}
int main()
{
String s1("Geeks"); // line 1
const char *name = "forGeeks";
s1 = name; // line 3
return 0;
}
Now, after line 3 s1 is a different object?
I always thought that after you construct object, you can't dereference it.
What you see is an assignment.
A copy assignment operator is automatically generated in your class, since you don't define one yourself. From the page I linked to:
If no user-defined copy assignment operators are provided for a class
type (struct, class, or union), the compiler will always declare one
as an inline public member of the class.
There is actually no dereferencing on line 3: there is only a replacement of an object with an another. The line 3 calls the constructor of String and assign the object to s1. So two different instances of String are being created but the constructor is not ´recalled´ on the first object but assign the created one to s1. The operator = is used as its default behavior which is to find if there is a constructor that matches the type given to the = operator.
On a side note, the dynamic memory is not freed at any point in the code making it a bad code sample.
Let's break it down.
s1 = name;
First, the compiler will construct a new String object using the constructor String(char const*). Then it uses the copy assignment operator to update s1 with this newly created object.
Since you did not define this operator, it simply does a copy of the class members, i.e. p and len: that's the default implementation the compiler generates for you. The old object is not cleaned up before so you are leaking memory... You should thus write your copy semantics using the copy and swap idiom:
class String {
char *p;
int len;
public:
// Your constructors...
// Swap function
friend void swap(String& s1, String& s2) {
using std::swap;
swap(s1.p, s2.p);
swap(s1.len, s2.len);
}
String(String const& other) : p(new char[other.len]()), len(other.len) {
std::copy(other.p, other.p + len, p);
}
String& operator=(String other) {
swap(*this, other);
return *this;
}
// And finally, a destructor:
/* virtual */ ~String() {
delete [] p;
}
};

why I cannot use "const string* sp = 0" to initialize in a constructor

my problem is that const string* p gives me an error. What is wrong with this? since I am not change the original value. const int& n = 0 works fine.
#include <iostream>
using namespace std;
class HasPtr
{
private:
int num;
string* sp;
public:
//constructor
HasPtr(const int& n = 0, const string* p = 0): num(n), sp(p) {}
//copy-constructor
HasPtr(const HasPtr& m): num(m.num), sp(m.sp) {}
//assignment operator
HasPtr& operator=(const HasPtr& m)
{
num = m.num;
sp = m.sp;
return *this;
}
//destructor
~HasPtr() {}
};
int main ()
{
return 0;
}
output Error is :
error: invalid conversion from ‘const std::string*’ to ‘std::string*’
private:
int num;
string* sp;
sp is non-const, but p is:
const string* p = 0
The result is that this sp(p) is basically this:
string* sp = (const string*)0;
It's complaining because doing this would remove the string's const-ness.
This is because your sp member is not const.
string* sp;
But your parameter p is a const. The result is that you are trying to assign a const pointer to a non-const pointer - hence the error.
To fix this, you need to declare sp const at as well.
const string* sp;
I think you got confused by the various meanings of const.
const string*sp;
declares a pointer to a constant object, which only allows access to constant methods of class string.
string*const sp;
declares a pointer to a string to be a constant member of the class, which you must initialise sp in the constructor and cannot change (except using const_cast<>).
const int&num;
in the argument list means that the function promises not to alter the value of the integer referred to by num, but it can of course copy its value (as you did). The corresponding operation for the string pointer would have been
HasPtr(string*const&p) : sp(p) { /* ... */ }
and would have been perfectly legal albeit rather unorthodox.