COMS W4995 C++ Deep Dive for C Programmers

Index of 2025-9/code/07

Parent directory
Makefile
mystring.cpp
mystring.h
vec1.cpp
vec2.cpp
vec3.cpp

Makefile

CC  = g++
CXX = g++

CFLAGS   = -g -Wall
CXXFLAGS = -g -Wall -std=c++14 -fno-elide-constructors

executables = vec1 vec2 vec3

.PHONY: default
default: $(executables)

vec1: vec1.o

vec2: vec2.o mystring.o

vec3: vec3.o mystring.o

vec3.o vec2.o mystring.o: mystring.h

.PHONY: clean
clean:
	rm -f *~ a.out core *.o $(executables)

.PHONY: all
all: clean default

mystring.cpp

#include <cstring>
#include <cstdio>

#include "mystring.h"

// default constructor

MyString::MyString() {
    data = new char[1];
    data[0] = '\0';

    len = 0;
}

// constructor

MyString::MyString(const char* p) {
    if (p) {
        len = strlen(p);
        data = new char[len + 1];
        strcpy(data, p);
    } else {
        data = new char[1];
        data[0] = '\0';
        len = 0;
    }
}

// destructor

MyString::~MyString() {
    delete[] data;
}

// copy constructor

MyString::MyString(const MyString& s) {
    len = s.len;

    data = new char[len + 1];
    strcpy(data, s.data);
}

// copy assignment

MyString& MyString::operator=(const MyString& rhs) {
    if (this == &rhs) {
        return *this;
    }

    // first, deallocate memory that 'this' used to hold

    delete[] data;

    // now copy from rhs

    len = rhs.len;

    data = new char[len + 1];
    strcpy(data, rhs.data);

    return *this;
}

// operator+

MyString operator+(const MyString& s1, const MyString& s2) {
    MyString temp;

    delete[] temp.data;

    temp.len = s1.len + s2.len;

    temp.data = new char[temp.len + 1];
    strcpy(temp.data, s1.data);
    strcat(temp.data, s2.data);

    return temp;
}

// put-to operator

std::ostream& operator<<(std::ostream& os, const MyString& s) {
    os << s.data;
    return os;
}

// get-from operator

std::istream& operator>>(std::istream& is, MyString& s) {
    // This is kinda cheating, but this is just to illustrate how this
    // function can work.

    std::string temp;
    is >> temp;

    delete[] s.data;

    s.len = strlen(temp.c_str());
    s.data = new char[s.len + 1];
    strcpy(s.data, temp.c_str());

    return is;
}

// operator[] - in real life this function should be declared inline

char& MyString::operator[](int i) {
    if (i < 0 || i >= len) {
        throw std::out_of_range{"MyString::op[]"};
    }
    return data[i];
}

// operator[] const - in real life this should be inline

const char& MyString::operator[](int i) const {
    // illustration of casting away constness
    return ((MyString&)*this)[i];

    // The C-style casting above works, but the proper way
    // to cast away constness in C++ is to do the following:
    //
    // return const_cast<MyString&>(*this)[i];
}

mystring.h

#ifndef __MYSTRING_H__
#define __MYSTRING_H__

#include <iostream>

class MyString {

public:

    // default constructor
    MyString();

    // constructor
    MyString(const char* p);

    // destructor
    ~MyString();

    // copy constructor
    MyString(const MyString& s);

    // copy assignment
    MyString& operator=(const MyString& s);

    // returns the length of the string
    int length() const { return len; }

    // operator+
    friend MyString operator+(const MyString& s1, const MyString& s2);

    // put-to operator
    friend std::ostream& operator<<(std::ostream& os, const MyString& s);

    // get-from operator
    friend std::istream& operator>>(std::istream& is, MyString& s);

    // operator[]
    char& operator[](int i);

    // operator[] const
    const char& operator[](int i) const;

private:

    char* data;

    int len;
};

#endif

vec1.cpp

#include <string>
#include <iostream>

template <typename T>
class Vec {
public:
    Vec() : sz{0}, cap{1}, a{new T[cap]} {}
    ~Vec() { delete[] a; }

    Vec(const Vec&) = delete;
    Vec& operator=(const Vec&) = delete;

    Vec(Vec&& tmp) : sz{tmp.sz}, cap{tmp.cap}, a{tmp.a} {
        tmp.sz = tmp.cap = 0;
        tmp.a = nullptr;
    }

    Vec& operator=(Vec&& tmp) {
        if (this != &tmp) {
            delete[] a;

            sz = tmp.sz;
            cap = tmp.cap;
            a = tmp.a;

            tmp.sz = tmp.cap = 0;
            tmp.a = nullptr;
        }
        return *this;
    }

    T& operator[](int i) { return a[i]; }
    const T& operator[](int i) const { return a[i]; }
    size_t size() const { return sz; }
    size_t capacity() const { return cap; }

    void push_back(const T& x) {
        if (sz == cap) {
            T* a2 = new T[cap * 2];
            cap *= 2;
            std::copy(a, a+sz, a2);
            delete[] a;
            a = a2;
        }
        a[sz++] = x;
    }
private:
    size_t sz;
    size_t cap;
    T* a;
};

template <typename T>
std::ostream& operator<<(std::ostream& os, const Vec<T>& vec) {
    for (size_t i = 0; i < vec.size(); i++) {
        os << vec[i] << " ";
    }
    std::cout << "(cap=" << vec.capacity() << ")" << std::flush;
    return os;
}

Vec<int> createIntVec() {
    Vec<int> tmp;
    for (int i = 0; i < 20; i++) {
        tmp.push_back(i);
        std::cout << tmp << std::endl;
    }
    return tmp;
}

Vec<std::string> createStrVec()
{
    Vec<std::string> tmp;
    for (char c = 'A'; c <= 'Z'; c++) {
        std::string s;
        s += c;
        tmp.push_back(s);
        std::cout << tmp << std::endl;
    }
    return tmp;
}

int main() {
    using namespace std;

    Vec<int> v { createIntVec() };

    Vec<string> v2 { createStrVec() };
}

vec2.cpp

#include <iostream>
#include "mystring.h"

template <typename T>
class Vec {
public:
    Vec() : sz{0}, cap{1}, a{new T[cap]} {}
    ~Vec() { delete[] a; }

    Vec(const Vec&) = delete;
    Vec& operator=(const Vec&) = delete;

    Vec(Vec&& tmp) : sz{tmp.sz}, cap{tmp.cap}, a{tmp.a} {
        tmp.sz = tmp.cap = 0;
        tmp.a = nullptr;
    }

    Vec& operator=(Vec&& tmp) {
        if (this != &tmp) {
            delete[] a;

            sz = tmp.sz;
            cap = tmp.cap;
            a = tmp.a;

            tmp.sz = tmp.cap = 0;
            tmp.a = nullptr;
        }
        return *this;
    }

    T& operator[](int i) { return a[i]; }
    const T& operator[](int i) const { return a[i]; }
    size_t size() const { return sz; }
    size_t capacity() const { return cap; }

    void push_back(const T& x) {
        if (sz == cap) {
            T* a2 = new T[cap * 2];
            cap *= 2;
            std::copy(a, a+sz, a2);
            delete[] a;
            a = a2;
        }
        a[sz++] = x;
    }
private:
    size_t sz;
    size_t cap;
    T* a;
};

template <typename T>
std::ostream& operator<<(std::ostream& os, const Vec<T>& vec) {
    for (size_t i = 0; i < vec.size(); i++) {
        os << vec[i] << " ";
    }
    std::cout << "(cap=" << vec.capacity() << ")" << std::flush;
    return os;
}

int main() {
    using namespace std;

    Vec<MyString> v;
    v.push_back("abc");
    v.push_back("def");
    MyString s{"xyz"};
    v.push_back(s);

    cout << "size:     " << v.size() << endl;
    cout << "capacity: " << v.capacity() << endl;
    for (size_t i = 0; i < v.capacity(); ++i) {
        cout << "v[" << i << "]: " << '"' << v[i] << '"' << endl;
    }
}

vec3.cpp

#include <iostream>
#include "mystring.h"

template <typename T>
class Vec {
public:
    Vec() : sz{0}, cap{1}, a{new T[cap]} {}
    ~Vec() { delete[] a; }

    Vec(const Vec&) = delete;
    Vec& operator=(const Vec&) = delete;

    Vec(Vec&& tmp) : sz{tmp.sz}, cap{tmp.cap}, a{tmp.a} {
        tmp.sz = tmp.cap = 0;
        tmp.a = nullptr;
    }

    Vec& operator=(Vec&& tmp) {
        if (this != &tmp) {
            delete[] a;

            sz = tmp.sz;
            cap = tmp.cap;
            a = tmp.a;

            tmp.sz = tmp.cap = 0;
            tmp.a = nullptr;
        }
        return *this;
    }

    T& operator[](int i) { return a[i]; }
    const T& operator[](int i) const { return a[i]; }
    size_t size() const { return sz; }
    size_t capacity() const { return cap; }

    void push_back(const T& x) {
        if (sz == cap) {
            T* a2 = new T[cap * 2];
            try {
                std::copy(a, a+sz, a2);
            } catch (...) {
                delete[] a2;
                throw;
            }
            cap *= 2;
            delete[] a;
            a = a2;
        }
        a[sz] = x;
        sz++;
    }
private:
    size_t sz;
    size_t cap;
    T* a;
};

template <typename T>
std::ostream& operator<<(std::ostream& os, const Vec<T>& vec) {
    for (size_t i = 0; i < vec.size(); i++) {
        os << vec[i] << " ";
    }
    std::cout << "(cap=" << vec.capacity() << ")" << std::flush;
    return os;
}

int main() {
    using namespace std;

    Vec<MyString> v;
    v.push_back("abc");
    v.push_back("def");
    MyString s{"xyz"};
    v.push_back(s);

    cout << "size:     " << v.size() << endl;
    cout << "capacity: " << v.capacity() << endl;
    for (size_t i = 0; i < v.capacity(); ++i) {
        cout << "v[" << i << "]: " << '"' << v[i] << '"' << endl;
    }
}