Skip to content

Debugging Questions

Twelve broken programs. For each, name the defect, explain what goes wrong and when, and give the fix. Then check the answers.

Work in this order — it finds most defects fast:

  1. Lifetime — is anything used after it is destroyed, or freed twice?
  2. virtual — is a polymorphic base missing a virtual destructor, or a function missing virtual?
  3. Copying — is an owning class relying on a compiler-supplied copy?
  4. const — is a non-const member called on a const object?
  5. Ownership — does something own what it should not, or fail to own what it should?

D1. A const reference cannot call the accessor.

class Sensor {
public:
    explicit Sensor(double c) : celsius_(c) {}
    double celsius() { return celsius_; }
private:
    double celsius_;
};
void report(const Sensor& s) { double v = s.celsius(); (void)v; }

D2. This compiles and prints Base, not Derived.

#include <iostream>
class Base { public: void speak() const { std::cout << "Base\n"; } };
class Derived : public Base { public: void speak() const { std::cout << "Derived\n"; } };
int main() {
    Derived d;
    const Base& r = d;
    r.speak();
}

D3. Deleting through the base.

#include <iostream>
class Base { public: ~Base() { std::cout << "~Base\n"; } };
class Derived : public Base { public: ~Derived() { std::cout << "~Derived\n"; } };
int main() {
    Base* p = new Derived;
    delete p;
}

D4. The copy shares the buffer.

#include <iostream>
class Buf {
public:
    explicit Buf(int n) : data_(new int[n]) {}
    ~Buf() { delete[] data_; }
private:
    int* data_;
};
int main() {
    Buf a{4};
    Buf b = a;
}

D5. Self-assignment.

class Blob {
public:
    Blob& operator=(const Blob& other) {
        delete[] data_;
        data_ = new int[other.n_];
        for (int i = 0; i < other.n_; ++i) data_[i] = other.data_[i];
        return *this;
    }
private:
    int  n_ = 0;
    int* data_ = nullptr;
};

D6. The derived overload hid the base one.

struct Base { void show() const {} };
struct Derived : Base { void show(int) const {} };
int main() { Derived d; d.show(); }

D7. Erasing while looping.

#include <vector>
int main() {
    std::vector<int> v{1, 2, 3, 4, 5};
    for (auto it = v.begin(); it != v.end(); ++it) {
        if (*it % 2 == 0) v.erase(it);
    }
}

D8. The exception is caught, but field() is missing.

#include <stdexcept>
#include <string>
class ValidationError : public std::runtime_error {
public:
    explicit ValidationError(std::string f) : std::runtime_error("bad " + f), field_(std::move(f)) {}
    const std::string& field() const { return field_; }
private:
    std::string field_;
};
int main() {
    try { throw ValidationError{"email"}; }
    catch (std::runtime_error e) { e.field(); }
}

D9. A destructor that throws.

#include <stdexcept>
class Guard {
public:
    ~Guard() { throw std::runtime_error("cannot close"); }
};
void work() { Guard g; throw std::runtime_error("boom"); }
int main() { try { work(); } catch (const std::exception&) {} }

D10. A dangling reference.

#include <vector>
class ReadingRoom {
public:
    void checkIn(int& id) { present_.push_back(&id); }
private:
    std::vector<int*> present_;
};
void session(ReadingRoom& room) {
    int temp = 42;
    room.checkIn(temp);
}

D11. The comparison makes std::sort misbehave.

class Item {
public:
    explicit Item(int v) : v_(v) {}
    bool operator<(const Item& o) const { return v_ <= o.v_; }
    int v() const { return v_; }
private:
    int v_;
};

D12. A factory returning a dangling pointer.

class Report { public: virtual ~Report() = default; };
class Summary : public Report {};
Report* make() {
    Summary s;
    return &s;
}

Then: check your answers against the answer key, and score yourself with Marking Yourself.