xSKILLby Infirals
Register

C++ for Beginners

0 of 24 notes read 0%

Reading progress is saved to your INFIRALS account. Sign in to keep this note marked as read.

Files, namespaces, and unique_ptr

Exceptions, STL, and ownership14 min readLesson 4 of 4

4.1 Reading and writing files

std::ofstream writes to a file, std::ifstream reads from one, and std::fstream does both. They come from <fstream> and behave like the console streams, so << and >> work the same way. A stream opens the file in its constructor and closes it in its destructor.

Opening can fail because of a missing directory, a permission problem, or a file that another program has locked. Check is_open() or test the stream's bool conversion after opening. A failed open is not an exception by default; the stream sets its failure state instead.

cpp
#include <fstream>
#include <iostream>

int main() {
    std::ofstream out("scores.txt");
    if (!out.is_open()) {
        std::cout << "Could not open file\n";
        return 1;
    }
    out << "Ada 90\n";
    out << "Ravi 72\n";
    out.close();
    std::cout << "File written\n";
    return 0;
}

Output

File written

4.1.1 Reading line by line

std::getline(stream, line) reads up to the next newline, stores the text without the newline, and returns the stream. The return value converts to bool, so while (std::getline(in, line)) stops at end of file or on a read error.

Mixing >> and getline on the same stream often leaves a newline behind. >> skips leading whitespace and stops before the character after the number, which may be a newline. Call std::cin.ignore() before getline when that leftover blocks the read. The same rule applies when a program reads a file with a mix of formatted reads and line reads.

cpp
#include <fstream>
#include <iostream>
#include <string>

int main() {
    std::ofstream out("notes.txt");
    out << "first\nsecond\n";
    out.close();

    std::ifstream in("notes.txt");
    std::string line;
    int number = 1;
    while (std::getline(in, line)) {
        std::cout << number << ": " << line << "\n";
        ++number;
    }
    return 0;
}

Output

1: first
2: second

4.1.2 Namespaces

A namespace groups names so that two libraries can define open without colliding. math::square and text::square are different functions. The :: operator selects a namespace, and names inside a namespace can refer to each other without the prefix.

using std::cout; brings one name into the current scope. using namespace std; brings every name, so keep it in small source files. A header that says using namespace forces that choice on every file that includes it.

cpp
#include <iostream>
#include <string>

namespace math {
int square(int value) { return value * value; }
}
namespace text {
std::string label() { return "square"; }
}
int main() {
    std::cout << text::label() << ": " << math::square(6) << "\n";
    return 0;
}

Output

square: 36

4.1.3 Namespaces across files

Declarations in a header usually sit inside a namespace. The matching source file defines the same names inside the same namespace, and callers use the qualified name. This keeps the header's names out of the global namespace and lets several libraries coexist.

An unnamed namespace inside a source file gives its contents internal linkage, which is a modern replacement for static at file scope. It is useful for helper functions that should not be visible outside the translation unit.

4.1.4 One owner per object

Raw new returns a pointer that someone must delete. If the code returns early, throws, or forgets, the object leaks. Smart pointers wrap the pointer in a class whose destructor deletes the object, so the same RAII rule that closes files also frees memory.

std::unique_ptr<T> owns exactly one object and deletes it when the pointer goes out of scope. std::make_unique<T>(args...) creates the object and the pointer together, which avoids a separate new expression. The pointer cannot be copied, because two owners would both delete the same object.

cpp
#include <iostream>
#include <memory>

class Widget {
public:
    explicit Widget(int id) : id_(id) { std::cout << "create " << id_ << "\n"; }
    ~Widget() { std::cout << "destroy " << id_ << "\n"; }
    int id() const { return id_; }
private:
    int id_;
};
int main() {
    auto widget = std::make_unique<Widget>(7);
    std::cout << "Widget " << widget->id() << "\n";
    return 0;
}

Output

create 7
Widget 7
destroy 7

4.1.5 Moving ownership

A unique_ptr can be moved. std::move(pointer) transfers ownership and leaves the source empty. A function that takes a unique_ptr by value takes ownership, and a function that takes a const reference or a raw pointer only borrows the object.

Containers of unique_ptr work because the elements can be moved. std::vector<std::unique_ptr<T>> stores each object separately and keeps the addresses stable when the vector grows. The container owns the pointers, and each pointer owns its object. Prefer unique_ptr over raw pointers for ownership, and reserve raw pointers for non-owning references.

cpp
#include <iostream>
#include <memory>
#include <vector>

int main() {
    std::vector<std::unique_ptr<int>> values;
    values.push_back(std::make_unique<int>(3));
    values.push_back(std::make_unique<int>(8));
    int total = 0;
    for (const auto& value : values) {
        total += *value;
    }
    std::cout << "Total: " << total << "\n";
    return 0;
}

Output

Total: 11

4.1.6 Related pointer types and mistakes

std::shared_ptr<T> allows several owners and frees the object when the last one goes away. It keeps a reference count, which costs time and memory. Use it only when ownership genuinely is shared. A std::weak_ptr<T> observes a shared object without keeping it alive.

Common mistakes include using a pointer after moving from it, creating two unique_ptrs from the same raw pointer, deleting a stack object, and forgetting #include <memory>. A file stream is also non-copyable; pass it by reference when a function needs to read or write.

4.1.7 Practice

  1. Write three names to a file with ofstream, then read them back with getline.
  2. Check is_open() on a path that does not exist and print a message.
  3. Put two functions with the same name in separate namespaces and call both.
  4. Create a unique_ptr to a class whose destructor prints, and watch the output at the end of the scope.
  5. Move a unique_ptr into a vector and print the values through the pointers.

Interview questions

3 questions from this note. Open one to see the answer; no account needed.

  • 01What does an ifstream object do when it goes out of scope?

    It closes the file. File streams are RAII objects, so the destructor releases the file handle, and an explicit close() is only needed when you want to release it earlier.

  • 02Why is `using namespace std;` risky in a header?

    A header is included into many translation units, so the directive pulls every std name into scope in all of them. That can create ambiguous names with other libraries and changes code you did not edit.

  • 03What happens when a unique_ptr is copied?

    It cannot be copied. The copy constructor is deleted because two unique_ptrs must not own the same object. Ownership moves with std::move, which leaves the source pointer empty.