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:
- Lifetime — is anything used after it is destroyed, or freed twice?
virtual— is a polymorphic base missing a virtual destructor, or a function missingvirtual?- Copying — is an owning class relying on a compiler-supplied copy?
const— is a non-constmember called on aconstobject?- 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.