Move ctor and move assignment operator with non pointer data - c++

I am new to C++ programming and here on the stackoverflow. I hope you will forgive me for my first questions here that are not so good written.
I am adding move constructor and move assignment operator in the class BigData.
Can someone tell me if they are written good?
Thanks a lot
Here is my struct Data that is used in BigData class. This is how I implemented these two methods.
struct Data {
//...
Data(Data &&_d)
{
data = _d.data;
_d.data = nullptr;
size = _d.size;
_d.size = 0;
}
Data& operator=(Data&& _d)
{
if (this != &_d)
{
delete[] data;
data = _d.data;
_d.data = nullptr;
size = _d.size;
_d.size = 0;
}
return *this;
}
unsigned char *data = nullptr;
unsigned int size = 0;
};
But now, I am not sure if I did it good. Since the Data is not the pointer I cant write the same thing as I did when I wrote the methods for Data struct.
Now when I do BigData bd2= move(bd1);
bd1 stays with data array... should I somehow delete it?
class BigData {
public:
BigData(BigData &&_bd)
{
m_data = _bd.GetData();
m_crc = _bd.GetCrc();
_bd.InvalidateCrc();
}
BigData& operator=(BigData &&_bd)
{
if (this != &_bd)
{
m_data = _bd.GetData();
m_crc = _bd.GetCrc();
}
return *this;
}
private:
Data m_data;
unsigned long int m_crc = -1;
};

Related

Custom container unnecessarily creating new elements instances when space is reserved

For my own education, I am trying to learn how to implement efficient custom containers in C++. I have now a basic working version of a my custom vector type. However, for some reason, when the vector has to be expanded to fit more elements (in which case a call to its inner 'reserve' function is made), it creates extra copies of elements.
To help explaining what I mean, I show below a minimum reproducible example. Let a minimum version of CustomVector class look like the following:
template<class T>
class CustomVector
{
private:
size_t m_size = 0;
size_t m_capacity = 1;
T *m_data = nullptr;
public:
CustomVector()
{
}
CustomVector(const size_t new_capacity)
{
m_capacity = new_capacity;
m_size = 0;
m_data = new T[m_capacity]();
}
~CustomVector()
{
if (m_data != nullptr)
delete[] m_data;
}
void reserve(size_t new_capacity)
{
if (m_data == nullptr)
{
m_capacity = new_capacity;
m_size = 0;
m_data = new T[m_capacity]();
}
else if (new_capacity > m_capacity)
{
T* new_data = new T[new_capacity]();
memmove(new_data, m_data, (m_size) * sizeof(T));
delete[] m_data;
m_capacity = new_capacity;
m_data = new_data;
}
}
void push_back(const T & value)
{
if (m_data == nullptr)
{
m_capacity = 1;
m_size = 0;
m_data = new T[m_capacity]();
m_data[0] = value;
}
else if (m_size + 1 >= m_capacity)
{
reserve(m_capacity*2);
}
else
{
m_data[m_size-1] = value;
m_size++;
}
}
};
Now, to facilitate seeing the problem, I also create a class called Object. Each new instance of such class that is created automatically receives an unique id number:
class Object
{
private:
static int idCounter;
public:
int id;
Object()
{
id = idCounter;
idCounter++;
}
};
int Object::idCounter = 0;
Lastly, here is how the main function of this example looks like:
int main()
{
CustomVector<Object> objects; //comment this line...
//std::vector<Object> objects; //...and uncomment this to try with std::vector
Object x;
printf("%d\n", x.id);
objects.push_back(x);
Object y;
printf("%d\n", y.id);
objects.push_back(y);
Object z;
printf("%d ", z.id);
system("Pause");
return 0;
}
The output, using my CustomVector as the container, is:
0 2 5
While the output using a std::vector as the container is:
0 1 2
The desirable behavior for me is exactly that of std::vector, that is, pushing back instances of classes should create full new temporary instances of such class.
Could someone help me understand what am I doing wrong?
The problem is most likely this line in the push_back function:
m_data = new T[m_capacity]();
This will cause the creation of m_capacity number of T objects, and therefore m_capacity calls to the T constructor. This is bad if the T constructor is expensive (not to mention some beginners do input and other things in the constructor).
What std::vector most likely does is keeping a buffer of bytes, and then when pushing back it does placement new to construct an object in place in some position in the buffer.

Dynamically allocated array inside a struct

I am learning about dynamic allocation of memory in C++. I've come across a problem for which I can't seem to find an answer.
In my program, I have a struct which goes like this:
struct Friend
{
string name = "";
int daysSinceContact = 0;
};
struct User
{
string name = "";
string password = "";
int numberOfFriends = 0;
Friend *friends = new Friend[numberOfFriends];
};
In my program, I create an array of users, which goes something like this:
int numberOfUsers = 5;
User *usersInformation = new User[numberOfUsers];
And it works fine. But I'd like to be able to add more friends to a chosen user, for example:
int nFriends = usersInformation[0].numberOfFriends;
usersInformation[0].numberOfFriends++;
usersInformation[0].friends[nFriends-1].name = "John";
usersInformation[0].friends[nFriends-1].daysSinceContact = 2;
I'm guessing that I should use a buffer to copy the information from the array containing information about friends, and do something like this:
delete[] usersInformation[0].friends[];
usersInformation[0].numberOfFriends++;
usersInformation[0].friends = new Friend[numberOfFriends];
And then copy it back and add information about the new friend. But when I've tried, it didn't work.
Do you have any tips?
The correct solution is to not use a manual array at all, use the STL's std::vector container instead, eg:
#include <vector>
#include <string>
struct Friend
{
std::string name = "";
int daysSinceContact = 0;
};
struct User
{
std::string name = "";
std::string password = "";
std::vector<Friend> friends;
};
...
std::vector<User> usersInformation(5);
...
Friend newFriend;
newFriend.name = "John";
newFriend.daysSinceContact = 2;
usersInformation[0].friends.push_back(newFriend);
// Or simpler:
usersInformation[0].friends.emplace_back(Friend{"John", 2});
That being said, if you really want to manage the array manually, you need to do something more like this instead (which includes implementing the Rule of Five to prevent corrupting and leaking memory):
#include <string>
struct Friend
{
std::string name = "";
int daysSinceContact = 0;
};
struct User
{
std::string name = "";
std::string password = "";
int numberOfFriends = 0;
Friend *friends = new Friend[numberOfFriends];
// default constructor (nothing extra that isn't already done above)
User() = default;
// copy constructor
User(const User &src) :
name(src.name),
password(src.password),
numberOfFriends(src.numberOfFriends),
friends(new Friend[numberOfFriends])
{
for(int i = 0; i < numberOfFriends; ++i)
friends[i] = src.friends[i];
}
// move constructor
User(User &&src) :
name(std::move(src.name)),
password(std::move(src.password)),
numberOfFriends(numberOfFriends),
friends(src.friends)
{
src.friends = nullptr;
src.numberOfFriends = 0;
}
// destructor
~User()
{
delete[] friends;
}
// copy assignment operator
User& operator=(const User &src)
{
if (this != &src)
{
Friend *newFriends = new Friend[src.numberOfFriends];
for(int i = 0; i < src.numberOfFriends; ++i)
newFriends[i] = src.friends[i];
name = src.name;
password = src.password;
delete[] friends;
friends = newFriends;
numberOfFriends = src.numberOfFriends;
}
return *this;
}
// move assignment operator
User& operator=(User &&src)
{
name := std::move(src.name);
password = std::move(src.password);
Friend *oldFriends = friends;
friends = src.friends;
src.friends = oldFriends;
int oldNumber = numberOfFriends;
numberOfFriends = src.numberOfFriends;
src.numberOfFriends = oldNumber;
return *this;
}
// addition helper
void addFriend(const std::string &name, int daysSinceContact = 0)
{
Friend *newFriends = new Friend[numberOfFriends + 1];
for(int i < 0; i < numberOfFriends; ++i)
newFriends[i] = friends[i];
newFriends[numberOfFriends].name = name;
newFriends[numberOfFriends].daysSinceContact = daysSinceContact;
delete[] friends;
friends = newFriends;
++numberOfFriends;
}
};
Or this, which is slightly safer in terms of memory management:
#include <string>
#include <utility>
#include <algorithm>
struct Friend
{
std::string name = "";
int daysSinceContact = 0;
};
struct User
{
std::string name = "";
std::string password = "";
int numberOfFriends = 0;
Friend *friends = new Friend[numberOfFriends];
// default constructor (nothing extra that isn't already done above)
User() = default;
// initializing constructor
User(int initialCapacity) :
friends(new Friend[initialCapacity])
{
}
// copy constructor
User(const User &src) :
User(src.numberOfFriends),
name(src.name),
password(src.password),
numberOfFriends(src.numberOfFriends)
{
std::copy(src.friends, src.friends + src.numberOfFriends, friends);
}
// move constructor
User(User &&src) :
name(std::move(src.name)),
password(std::move(src.password)),
numberOfFriends(0),
friends(nullptr)
{
std::swap(friends, src.friends);
std::swap(numberOfFriends, src.numberOfFriends);
}
// destructor
~User()
{
delete[] friends;
}
// copy assignment operator
User& operator=(const User &src)
{
if (this != &src)
User(src).swap(*this);
return *this;
}
// move assignment operator
User& operator=(User &&src)
{
src.swap(*this);
return *this;
}
// swap helper
void swap(User &other)
{
std::swap(name, other.name);
std::swap(password, other.password);
std::swap(numberOfFriends, other.numberOfFriends);
std::swap(friends, other.friends);
}
// addition helper
void addFriend(const std::string &name, int daysSinceContact = 0)
{
User temp(numberOfFriends + 1);
std::copy(friends, friends + numberOfFriends, temp.friends);
temp.friends[numberOfFriends] = Friend{name, daysSinceContact};
std::swap(friends, temp.friends);
++numberOfFriends;
}
};
Either way, then you can do this:
User *usersInformation = new User[5];
...
usersInformation[0].addFriend("John", 2);
...
delete[] usersInformation;

Template Class Assignment Operator Overloading

I'm having a little trouble with the following:
I'm writing a map abstract data type for some coursework & I've come across a problem whilst trying to assign an object of my class (MapEntry - below) to an array of the same type in the class MapADT. It tells me that:
Error 1 error C2679: binary '=' : no operator found which takes a right-hand operand of type 'MapEntry *' (or there is no acceptable conversion) c:\users\cross_000\documents\visual studio 2013\projects\objectorientedmethodsasignment1\mapadt\mapadt.h 14
So I thought I would write my own Assignment operator override. I've done this in the MapEntry class definition but the compiler doesn't seem to recognize it when I try to initialize the array in the constructor on MapADT - below.
Any help would be greatly appreciated.
#pragma once
template <class Tk, class Tc>
class MapEntry
{
private:
Tk key;
Tc contents;
bool isPopulated;
public:
MapEntry() {
}
MapEntry(Tk keyInput, Tc contentsInput) {
key = keyInput;
contents = contentsInput;
isPopulated = true;
}
MapEntry(Tk keyInput, Tc contentsInput, bool isPopulatedInput) {
key = keyInput;
contents = contentsInput;
isPopulated = isPopulatedInput;
}
~MapEntry() {
//TODO
}
Tk getKey() {
return key;
}
void setKey(Tk keyInput) {
key = keyInput;
}
Tc getContents() {
return contents;
}
void setContents(Tc contentsInput) {
contents = contentsInput;
}
bool getIsPopulated() {
return isPopulated;
}
void setIsPopulated(bool isPopulatedInput) {
isPopulated = isPopulatedInput;
}
MapEntry<Tk, Tc>& operator=(const MapEntry<Tk, Tc> & lst)
{
clear();
copy(lst);
return *this;
}
};
MapADT.h
#pragma once
#include "MapEntry.h"
template <class Tk, class Tc>
class MapADT
{
private:
int mapSize = 1000;
MapEntry<Tk, Tc> *map;
public:
MapADT() {
map = new MapEntry<Tk, Tc>[mapSize];
for (int i = 0; i < mapSize; i++) {
map[i] = new MapEntry<Tk, Tc>(NULL, NULL, false);
}
}
}
There's more to the MapADT class but I don't think it's relevant. If you need to see the whole thing I can add it.
map[i] is not an pointer to MapEntry.
Not sure if you want
map[i] = MapEntry<Tk, Tc>(NULL, NULL, false);
or
MapEntry<Tk, Tc>** map;
MapADT() {
map = new MapEntry<Tk, Tc>*[mapSize];
for (int i = 0; i < mapSize; i++) {
map[i] = new MapEntry<Tk, Tc>(NULL, NULL, false);
}
}
to solve your issue.
In this line
map = new MapEntry<Tk, Tc>[mapSize];
you allocate an array of MapEntry<Tk, Tc>, and default constructors are called for all of them. There's no need in subsequent for loop at all, you should just write proper initialization in MapEntry::MapEntry(), which is currently empty.

How to sort custom class objects iside of an array

I am wondering how to sort an array that contains objects of a custom class. I am trying to apply different sorting algorithms but in the swapping something goes wrong.
Here is my Code:
class RaceCar
{
private:
char* _brand;
char* _model;
double _price;
int _horse_power;
public:
//Other code
RaceCar(const RaceCar& rc):_price(rc._price), _horse_power(rc._horse_power)
{
_brand = new char[strlen(rc._brand)+1];
strcpy(_brand, rc._brand);
_model = new char[strlen(rc._model)+1];
strcpy(_model,rc._model);
}
RaceCar& operator=(const RaceCar& rc)
{
if(this != &rc)
{
delete _brand;
delete _model;
_brand = new char[strlen(rc._brand)+1];
strcpy(_brand, rc._brand);
_model = new char[strlen(rc._model)+1];
strcpy(_model, rc._model);
_price = rc._price;
_horse_power = rc._horse_power;
}
return *this;
}
bool operator<(const RaceCar& rc)
{
return (this->_price/this->_horse_power) > (rc._price/rc._horse_power);
}
//Other code
};
And this is the class that contains an array of RaceCars. I am trying to implement SortCars() method that orders the RaceCar objects inside the array of cars:
class RaceCarGarage
{
private:
RaceCar* _cars;
int _max_cars;
int _curr_occupied;
public:
RaceCarGarage():_cars(NULL), _max_cars(0),_curr_occupied(0){}
RaceCarGarage(const RaceCar& car, int max_cars)
:_max_cars(max_cars), _curr_occupied(0)
{
_cars = new RaceCar[_max_cars];
}
~RaceCarGarage()
{
delete _cars;
}
void AddCar(const RaceCar& car)
{
if(_curr_occupied < _max_cars)
{
_cars[_curr_occupied] = car;
_curr_occupied += 1;
}
}
void DisplayCars()
{
if(_curr_occupied > 0)
{
for(int i=0 ; i<_curr_occupied ; i++)
{
cout<<(i+1)<<". ";
(_cars+i)->Display();
}
}
}
void SortCars()
{
if(_curr_occupied > 1)
{
for(int i=0 ; i<_curr_occupied ; i++)
{
for(int j = i+1 ; j<_curr_occupied ; j++)
{
if(_cars[j]<_cars[i])
{
RaceCar buffer = _cars[i];
_cars[i] = _cars[j];
_cars[j] = buffer;
}
}
}
}
}
};
The problem with the swapping is that you use the traditional way to do:
temp = a // operator=
a = b // operator=
b = temp; // operator=
However, if you write:
RaceCar temp = a; // Copy constructor gets called (see comment on standard)
a = b; // operator=
b = temp; // operator=
The default copy constructor, just copies member by member, so just copies your pointer. So at the end, your temp and your will try to delete twice the same object pointed to.
Remark on assignment initializer :
For a type T, a statement in form T a = b; is an initializer.
The ISO standard C++ in section 12.6.1 point 1 explains "a single assignment-expression can be specified as an initializer using the = form of initialization. Either direct-initialization semantics or copy-initialization semantics apply;"

C : Double free or corruption, can't find error

This error is making me mad...
Valgrinding the program shows that the delete[] in DataPackage::~DataPackage (line 40) creates the problem. But if I remove it, the program will leak.
So, how to repair it and what have I done wrong?
main.cxx
#include "main.h" // currently includes DataPackage.h only
DataPackage aTestFunction(){
return DataPackage("hello",5);
}
int main(){
DataPackage pack1 = aTestFunction(), pack2 = aTestFunction();
pack1 = pack2;
for(unsigned int i = 0; i < pack1.getLength(); i++){
printf("0x%02x ", *(pack1.getData()+i)&0xff);
}
return 0;
}
DataPackage.cxx
#include "DataPackage.h" // defines only the class, and private members m_data (char*) and m_length (size_t), includes <cstdio> and <cstring>
DataPackage::DataPackage(){
m_data = NULL;
m_length = 0;
}
DataPackage::DataPackage(string data){
m_data = NULL;
setData(data.c_str(),data.length()+1);
}
DataPackage::DataPackage(const char *data, size_t length) {
m_data = NULL;
setData(data,length);
}
DataPackage::DataPackage(const DataPackage &pack) {
m_data = NULL;
setData(pack.m_data,pack.m_length);
}
const char* DataPackage::getData(){
return m_data;
}
void DataPackage::setData(const char *newdata,size_t newlength){
char* tmpdata = new char[newlength];
m_length = newlength;
memcpy(tmpdata,newdata,m_length);
delete[] m_data;
m_data = tmpdata;
}
size_t DataPackage::getLength(){
return m_length;
}
DataPackage::~DataPackage() {
delete[] m_data;
}
You forgot the Rule of Three and didn't provide a copy-assignment operator. So, when assigning one DataPackage from another, both end up with a pointer to the same buffer, and both try to delete it.
I'd throw that class away and use std::string or std::vector<char> instead.
The error is caused by 'pack1 = pack2;' in main.cxx
Implement DataPackage::operator= to fix it.
You need implement an assignment operator that manages m_data. The automatically generated implementation will just copy the pointer, not copy the pointed-to array. The assignment operator could look like this:
DataPackage& DataPackage::operator=(const DataPackage &other) {
setData(other.m_data, other.m_length);
return *this;
}
Try :
void DataPackage::setData(const char *newdata,size_t newlength){
char* tmpdata = new char[newlength];
m_length = newlength;
memcpy(tmpdata,newdata,m_length);
if (m_data)
{
delete[] m_data;
}
m_data = tmpdata;
}
From what i see your copy constructor set m_data to NULL and call setData just after. In setData your are basically doing delete[] NULL;