Alright, I've officially decided, I'm not going to give up on this!
So, I tried to take some of your advice to heart, phantom. Forgive me if this code is still awful but and you'll like this (hopefully), it seems like my code is now constructor-excepting safe. I think. Basically, I defined a non-trivial constructor that throws. However, I did some more thorough testing and it looks like it's up to the programmer (the theoretical user of my code) to define their own RAII-style classes to prevent leaks when their constructors throw exceptions.
So I guess if a user wants to define a constructor that throws, they need some sort of handling on their part. I'm still kind of unsure about this but I have my container's insert() routine throw the error that bubbles up from the bottom.
Here's the code:
container.hpp
Code:
#ifndef CONTAINER_HPP_
#define CONTAINER_HPP_
#include <vector>
#include <exception>
#include <iostream>
#include <memory>
#include <cstring>
template<class T>
class Element {
private:
char buffer_[sizeof(T)];
bool alive_;
public:
Element(void) {
memset(buffer_, 0, sizeof(T));
alive_ = false;
}
template<class... Args>
Element(Args&&... args) {
std::cout << "Constructing a new Element by copy..." << std::endl;
try {
new(buffer_) T{args...};
alive_ = true;
}
catch(std::exception &e) {
memset(buffer_, 0, sizeof(T));
alive_ = false;
std::cout << e.what() << std::endl;
throw e;
}
}
T value(void) {
T *data = reinterpret_cast<T*>(buffer_);
return *data;
}
};
template<class T>
class Block {
private:
std::unique_ptr<Element<T>[]> elements_;
size_t size_;
public:
Block(size_t num_elems) {
std::cout << "Constructing a new Block..." << std::endl;
try {
size_ = num_elems;
elements_ = std::unique_ptr<Element<T>[]>(new Element<T>[num_elems]);
}
catch(std::exception &e) {
size_ = 0;
std::cout << e.what() << std::endl;
}
}
Block& operator[](size_t index) {
if (index >= size_) throw std::out_of_range{"Invalid index"};
return elements_[index];
}
size_t size(void) const {
return size_;
}
Element<T>* data(void) {
return elements_.get();
}
};
template<class T>
class Container {
private:
std::vector<std::unique_ptr<Block<T>>> blocks_;
std::vector<Element<T>*> free_list_;
size_t block_size_;
size_t size_;
public:
Container(void) {
std::cout << "Constructing the Container..." << std::endl;
block_size_ = 16;
size_ = 0;
free_list_.reserve(block_size_);
try {
blocks_.push_back(std::unique_ptr<Block<T>>(new Block<T>(block_size_)));
Block<T> &block = *blocks_[0];
for (size_t i = 0; i < block.size(); ++i)
free_list_.emplace_back(block.data() + i);
}
catch (std::exception &e) {
std::cout << e.what() << std::endl;
}
}
template<class... Args>
Element<T>* insert(Args&&... args) {
std::cout << "Inserting..." << std::endl;
try {
Element<T>* element = new(free_list_.back()) Element<T>(args...);
++size_;
free_list_.pop_back();
return element;
}
catch (std::exception &e) {
throw e;
}
}
};
#endif
main.cpp
Code:
#include <iostream>
#include <stdexcept>
#include <string>
#include "container.hpp"
class Test {
private:
std::unique_ptr<int[]> data_;
int x_;
public:
Test(int x) {
std::cout << "Attempting to construct Test::Test(int)" << std::endl;
data_ = std::move(std::unique_ptr<int[]>{new int[16]});
if (x == -1) {
x_ = x;
return;
}
throw std::invalid_argument("Invalid value passed to Test!");
}
};
int main(void) {
// Container<int> c;
// for (int i = 0; i < 8; ++i) {
// auto elem = c.insert(i);
// std::cout << elem->value() << std::endl;
// }
Container<Test> c;
try {
c.insert(3);
}
catch (std::exception &e) {
}
return 0;
}
I'm kind of new to all of this stuff so any advice from anyone would be much appreciated! I'm looking at you, Elysia and laserlight. And maybe Yarin too. I count on you guys lol.