COMS W4995 C++ Deep Dive for C Programmers

Index of 2025-9/code/17

Parent directory
Makefile
sharedptr.h
sharedptr1.cpp
sharedptr2.cpp
uniqueptr.cpp

Makefile

CC  = g++
CXX = g++

CFLAGS   = -g -Wall
CXXFLAGS = -g -Wall -std=c++20

# turn off warnings about unused variables
CXXFLAGS += -Wno-unused-variable

executables = sharedptr1 sharedptr2 uniqueptr

.PHONY: default
default: $(executables)

sharedptr1: sharedptr1.o

sharedptr2: sharedptr2.o

uniqueptr: uniqueptr.o

sharedptr1.o: sharedptr.h

sharedptr2.o: sharedptr.h

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

.PHONY: all
all: clean default

sharedptr.h

template <typename T>
class SharedPtr {
    T*   ptr;    // the underlying pointer
    int* count;  // the reference count
public:
    explicit SharedPtr(T* p = nullptr) : ptr{p}, count{new int{1}} {}
    ~SharedPtr() {
        if (--*count == 0) {
            delete count;
            delete ptr;
        }
    }

    SharedPtr(const SharedPtr<T>& sp) : ptr(sp.ptr), count(sp.count) {
        ++*count; 
    }
    SharedPtr<T>& operator=(const SharedPtr<T>& sp) {
        if (this != &sp) {
            // first, detach.
            if (--*count == 0) {
                delete count;
                delete ptr;
            }
            // attach to the new object.
            ptr = sp.ptr;
            count = sp.count;
            ++*count;
        }
        return *this;
    }

    T& operator*() const { return *ptr; }
    T* operator->() const { return ptr; }

    operator void*() const { return ptr; } // enables "if (sp) ..."
    T* get_ptr() const { return ptr; } // access to underlying ptr
    int get_count() const { return *count; } // access to reference count
};

sharedptr1.cpp

#include <string>
#include <vector>
#include <iostream>
#include <iomanip>
#include <utility>
#include <memory>
#include "sharedptr.h"
using namespace std;

auto create_vec1() {

    // SharedPtr<T> replaces T* for heap-allocated object.
    // That is, instead of doing
    //
    //     string* p { new string{"hello"} };
    //
    // We do the following:

    SharedPtr<string> p1 { new string{"hello"} };
    SharedPtr<string> p2 { new string{"c"} };
    SharedPtr<string> p3 { new string{"students"} };

    vector v { p1, p2, p3 };
    return v;
}

template <typename T, typename... Args>
SharedPtr<T> MakeSharedPtr(Args&&... args) {
    return SharedPtr<T>{new T{forward<Args>(args)...}};
}

auto create_vec2() {
    auto p1 { MakeSharedPtr<string>("hello") };
    auto p2 { MakeSharedPtr<string>("c") };
    auto p3 { MakeSharedPtr<string>("students") };

    vector v { p1, p2, p3 };
    return v;
}

auto create_vec3() {
    auto p1 { make_shared<string>("hello") };
    auto p2 { make_shared<string>("c") };
    auto p3 { make_shared<string>("students") };
    static_assert(is_same_v<decltype(p3), shared_ptr<string>>);

    vector v { p1, p2, p3 };
    return v;
}

int main() {
    vector v1 = create_vec1();
    // vector v1 = create_vec2();
    // vector v1 = create_vec3();

    for (auto p : v1) { cout << *p + " "; } cout << '\n';
    // for (auto& p : v1) { cout << *p + " "; } cout << '\n';
    // for (const auto& p : v1) { cout << *p + " "; } cout << '\n';
    // for (auto&& p : v1) { cout << *p + " "; } cout << '\n';

    vector v2 = v1;
    *v2[1] = "c2cpp";
    *v2[2] = "hackers"; 

    // Print v1 again
    for (auto&& p : v1) { cout << *p + " "; } cout << '\n';

    cout << "\nReference count demo:\n";
    {
        SharedPtr<string> p1 { new string{"hello"} };
        cout << "before foo(), reference count: " << p1.get_count() << '\n';

        auto foo = [](SharedPtr<string> p) {
            cout << "inside foo(), reference count: " << p.get_count() << '\n';
        };
        foo(p1);

        cout << "after foo(), reference count: " << p1.get_count() << '\n';
    }
}

sharedptr2.cpp

#include <string>
#include <vector>
#include <iostream>
#include <iomanip>
#include <utility>
#include <memory>
#include <cassert>
#include <atomic>
#include "sharedptr.h"
using namespace std;

int main() {
    // 1. make_shared<T>() combines heap allocations
    {
        // string*            p0 { new string(50, 'A') };
        // shared_ptr<string> p1 { new string(50, 'B') };
        // shared_ptr<string> p2 { make_shared<string>(50, 'C') };
    }

    // 2. shared_ptr to an array and custom deleter
    {
        // Invalid delete
        // SharedPtr<string> p0 { new string[2] {"hello", "c2cpp"} };
        // cout << *p0 << ' ' << p0.get_ptr()[1] << '\n';

        // Invalid delete
        // shared_ptr<string> p1 { new string[2] {"hello", "c2cpp"} };
        // cout << *p1 << ' ' << p1.get()[1] << '\n';

        // Custom deleter
        // shared_ptr<string> p2 { new string[2] {"hello", "c2cpp"},
        //     [](string* s) { delete[] s; }
        // };
        // cout << *p2 << ' ' << p2.get()[1] << '\n';

        // shared_ptr parameterized with array type
        // shared_ptr<string[]> p3 { new string[2] {"hello", "c2cpp"} };
        // cout << p3[0] << " " << p3[1] << '\n';
    }

    // 3. Aliasing
    {
    //     shared_ptr<pair<string,int>> p1 { new pair{"hi"s, 5} };
    //     shared_ptr<int>              p2 { p1, &p1->second };

    //     cout << p1->first << ' ' << *p2 << '\n';

    //     assert(p1.use_count() == 2 && p2.use_count() == 2);
    }

    // 4. weak_ptr
    {
    //     auto weak_test = [](weak_ptr<string> wp) {
    //         shared_ptr<string> sp = wp.lock();
    //         if (sp) {
    //             assert(sp.use_count() == 2);
    //             cout << *sp << '\n';
    //         } else {
    //             cout << "weak_ptr expired\n";
    //         }
    //     };

    //     weak_ptr<string> wp;

    //     {
    //         auto sp = make_shared<string>("hello");
    //         assert(sp.use_count() == 1);

    //         wp = sp;                     // weak_ptr does not increase
    //         assert(sp.use_count() == 1); // the shared_ptr's ref count

    //         weak_test(wp); // sp is alive, so is wp
    //     }

    //     weak_test(wp); // sp is gone, so wp is expired
    }
}

uniqueptr.cpp

#include <string>
#include <vector>
#include <iostream>
#include <iomanip>
#include <utility>
#include <memory>
#include <cassert>
using namespace std;

unique_ptr<string[]> create_array(size_t n, string s) {
    auto up = make_unique<string[]>(n);
    for (size_t i = 0; i < n; ++i) {
        up[i] = s;
    }
    return up;
}

int main() {
    size_t n = 1000;
    unique_ptr<string[]> up1 = create_array(n, "hello");
  
    // unique_ptr holds just a pointer
    static_assert(sizeof(up1) == 8);

    for (size_t i = 0; i < n; ++i) { assert(up1[i] == "hello"s); }

    unique_ptr<string[]> up2;
    assert(!up2);

    // This doesn't compile -- unique_ptr cannot be copied
    // up2 = up1;

    // But it can be moved
    up2 = std::move(up1);
    assert(!up1 && up2);

    // It can also be moved to a shared_ptr
    shared_ptr<string[]> sp { std::move(up2) };
    assert(!up2 && sp.use_count() == 1);

    for (size_t i = 0; i < n; ++i) { assert(sp[i] == "hello"s); }
}