diff --git a/include/vkcv/Handles.hpp b/include/vkcv/Handles.hpp
index ad48a1ecbd41f75286cc33e88e62699468a5f11f..7b500ea514f1034e9822f9c12936b5e63ba3225e 100644
--- a/include/vkcv/Handles.hpp
+++ b/include/vkcv/Handles.hpp
@@ -5,13 +5,63 @@
  * @brief Central header file for all possible handles that the framework will hand out.
  */
 
-#include <cstdint>
+#include <iostream>
 
 namespace vkcv
 {
+	class Handle {
+		friend std::ostream& operator << (std::ostream& out, const Handle& handle);
+		
+	private:
+		uint64_t m_id;
+	
+	protected:
+		Handle();
+		
+		explicit Handle(uint64_t id);
+		
+		[[nodiscard]]
+		uint64_t getId() const;
+	
+	public:
+		virtual ~Handle() = default;
+		
+		Handle(const Handle& other) = default;
+		Handle(Handle&& other) = default;
+		
+		Handle& operator=(const Handle& other) = default;
+		Handle& operator=(Handle&& other) = default;
+		
+		explicit operator bool() const;
+		bool operator!() const;
+		
+	};
+	
+	std::ostream& operator << (std::ostream& out, const Handle& handle);
+	
     // Handle returned for any buffer created with the core/context objects
-    struct BufferHandle     {uint64_t id;};
-    struct PassHandle       {uint64_t id;};
-    struct PipelineHandle   {uint64_t id;};
-    struct ResourcesHandle  {uint64_t id;};
+    class BufferHandle : public Handle {
+    	friend class BufferManager;
+	private:
+		using Handle::Handle;
+    };
+	
+	class PassHandle : public Handle {
+		friend class PassManager;
+	private:
+		using Handle::Handle;
+	};
+	
+	class PipelineHandle : public Handle {
+		friend class PipelineManager;
+	private:
+		using Handle::Handle;
+	};
+	
+	class ResourcesHandle : public Handle {
+		friend class DescriptorManager;
+	private:
+		using Handle::Handle;
+	};
+	
 }
diff --git a/projects/first_triangle/src/main.cpp b/projects/first_triangle/src/main.cpp
index 275c87cf67f059c5896272639363733634e26ca2..b9458e4c20cbe8fa2670bcf782f7485966647b6b 100644
--- a/projects/first_triangle/src/main.cpp
+++ b/projects/first_triangle/src/main.cpp
@@ -78,7 +78,7 @@ int main(int argc, const char** argv) {
 	vkcv::PassConfig trianglePassDefinition({present_color_attachment});
 	vkcv::PassHandle trianglePass = core.createPass(trianglePassDefinition);
 
-	if (trianglePass.id == 0)
+	if (!trianglePass)
 	{
 		std::cout << "Error. Could not create renderpass. Exiting." << std::endl;
 		return EXIT_FAILURE;
@@ -90,7 +90,8 @@ int main(int argc, const char** argv) {
 
 	const vkcv::PipelineConfig trianglePipelineDefinition(triangleShaderProgram, windowWidth, windowHeight, trianglePass);
 	vkcv::PipelineHandle trianglePipeline = core.createGraphicsPipeline(trianglePipelineDefinition);
-	if (trianglePipeline.id == 0)
+	
+	if (!trianglePipeline)
 	{
 		std::cout << "Error. Could not create graphics pipeline. Exiting." << std::endl;
 		return EXIT_FAILURE;
@@ -111,7 +112,7 @@ int main(int argc, const char** argv) {
 
 		sets.push_back(set);
         auto resourceHandle = core.createResourceDescription(sets);
-        std::cout << "Resource " << resourceHandle.id << " created." << std::endl;
+        std::cout << "Resource " << resourceHandle << " created." << std::endl;
 	}
 
 	/*
diff --git a/src/vkcv/BufferManager.cpp b/src/vkcv/BufferManager.cpp
index e9b5112888782ae5be373f8a63777b7fc3503713..afff7d4b3e3fc78a6dbb7535162857f21812721c 100644
--- a/src/vkcv/BufferManager.cpp
+++ b/src/vkcv/BufferManager.cpp
@@ -9,7 +9,7 @@
 namespace vkcv {
 	
 	BufferManager::BufferManager() noexcept :
-		m_core(nullptr), m_buffers(), m_stagingBuffer(BufferHandle{ UINT64_MAX })
+		m_core(nullptr), m_buffers(), m_stagingBuffer(BufferHandle())
 	{
 	}
 	
@@ -22,8 +22,8 @@ namespace vkcv {
 	}
 	
 	BufferManager::~BufferManager() noexcept {
-		for (size_t id = 0; id < m_buffers.size(); id++) {
-			destroyBuffer(BufferHandle{ id });
+		for (uint64_t id = 0; id < m_buffers.size(); id++) {
+			destroyBuffer(BufferHandle(id));
 		}
 	}
 	
@@ -179,7 +179,7 @@ namespace vkcv {
 	}
 	
 	vk::Buffer BufferManager::getBuffer(const BufferHandle& handle) const {
-		const uint64_t id = handle.id;
+		const uint64_t id = handle.getId();
 		
 		if (id >= m_buffers.size()) {
 			return nullptr;
@@ -191,7 +191,7 @@ namespace vkcv {
 	}
 	
 	vk::DeviceMemory BufferManager::getDeviceMemory(const BufferHandle& handle) const {
-		const uint64_t id = handle.id;
+		const uint64_t id = handle.getId();
 		
 		if (id >= m_buffers.size()) {
 			return nullptr;
@@ -203,7 +203,7 @@ namespace vkcv {
 	}
 	
 	void BufferManager::fillBuffer(const BufferHandle& handle, void *data, size_t size, size_t offset) {
-		const uint64_t id = handle.id;
+		const uint64_t id = handle.getId();
 		
 		if (size == 0) {
 			size = SIZE_MAX;
@@ -232,7 +232,7 @@ namespace vkcv {
 			memcpy(mapped, data, max_size);
 			device.unmapMemory(buffer.m_memory);
 		} else {
-			auto& stagingBuffer = m_buffers[ m_stagingBuffer.id ];
+			auto& stagingBuffer = m_buffers[ m_stagingBuffer.getId() ];
 			
 			StagingStepInfo info;
 			info.data = data;
@@ -253,7 +253,7 @@ namespace vkcv {
 	}
 	
 	void* BufferManager::mapBuffer(const BufferHandle& handle, size_t offset, size_t size) {
-		const uint64_t id = handle.id;
+		const uint64_t id = handle.getId();
 		
 		if (size == 0) {
 			size = SIZE_MAX;
@@ -281,7 +281,7 @@ namespace vkcv {
 	}
 	
 	void BufferManager::unmapBuffer(const BufferHandle& handle) {
-		const uint64_t id = handle.id;
+		const uint64_t id = handle.getId();
 		
 		if (id >= m_buffers.size()) {
 			return;
@@ -300,7 +300,7 @@ namespace vkcv {
 	}
 	
 	void BufferManager::destroyBuffer(const BufferHandle& handle) {
-		const uint64_t id = handle.id;
+		const uint64_t id = handle.getId();
 		
 		if (id >= m_buffers.size()) {
 			return;
diff --git a/src/vkcv/DescriptorManager.cpp b/src/vkcv/DescriptorManager.cpp
index bcebb142af2f29c23e77185112cbb83a827e41ba..c42db30fb4d8d72e340a9db53afb3fcf6d1058d0 100644
--- a/src/vkcv/DescriptorManager.cpp
+++ b/src/vkcv/DescriptorManager.cpp
@@ -8,7 +8,7 @@ namespace vkcv
     descriptorSetLayouts{std::move(layouts)}
     {}
     DescriptorManager::DescriptorManager(vk::Device device) noexcept:
-        m_Device{ device }, m_NextResourceDescriptionID{ 1 }
+        m_Device{ device }, m_NextResourceDescriptionID{ 0 }
     {
         /**
          * Allocate a set size for the initial pool, namely 1000 units of each descriptor type below.
@@ -64,7 +64,7 @@ namespace vkcv
             if(m_Device.createDescriptorSetLayout(&layoutInfo, nullptr, &layout) != vk::Result::eSuccess)
             {
                 std::cout << "FAILED TO CREATE DESCRIPTOR SET LAYOUT" << std::endl;
-                return ResourcesHandle{0};
+                return ResourcesHandle();
             };
             vk_setLayouts.push_back(layout);
         }
@@ -79,11 +79,11 @@ namespace vkcv
             for(const auto &layout : vk_setLayouts)
                 m_Device.destroy(layout);
 
-            return ResourcesHandle{0};
+            return ResourcesHandle();
         };
 
         m_ResourceDescriptions.emplace_back(vk_sets, vk_setLayouts);
-        return ResourcesHandle{m_NextResourceDescriptionID++};
+        return ResourcesHandle(m_NextResourceDescriptionID++);
     }
 
     vk::DescriptorType DescriptorManager::convertDescriptorTypeFlag(DescriptorType type) {
diff --git a/src/vkcv/Handles.cpp b/src/vkcv/Handles.cpp
index 1337a132ae0b8721bd2776e24212f73dfb94ae46..bd465d4e71da31c554d82ceb4b8bce7b0ee04129 100644
--- a/src/vkcv/Handles.cpp
+++ b/src/vkcv/Handles.cpp
@@ -1 +1,33 @@
 #include "vkcv/Handles.hpp"
+
+namespace vkcv {
+	
+	Handle::Handle() :
+		m_id(UINT64_MAX)
+	{}
+	
+	Handle::Handle(uint64_t id) :
+		m_id(id)
+	{}
+	
+	uint64_t Handle::getId() const {
+		return m_id;
+	}
+	
+	Handle::operator bool() const {
+		return (m_id < UINT64_MAX);
+	}
+	
+	bool Handle::operator!() const {
+		return (m_id == UINT64_MAX);
+	}
+	
+	std::ostream& operator << (std::ostream& out, const Handle& handle) {
+		if (handle) {
+			return out << "[Handle: " << handle.getId() << "]";
+		} else {
+			return out << "[Handle: none]";
+		}
+	}
+	
+}
diff --git a/src/vkcv/PassManager.cpp b/src/vkcv/PassManager.cpp
index 820bf3ff8a84edb9d070c22c60327cf7cb661ee7..d69024a805f45cda549365c7cc97fc57f59ef926 100644
--- a/src/vkcv/PassManager.cpp
+++ b/src/vkcv/PassManager.cpp
@@ -50,7 +50,7 @@ namespace vkcv
     PassManager::PassManager(vk::Device device) noexcept :
     m_Device{device},
     m_RenderPasses{},
-    m_NextPassId{1}
+    m_NextPassId(0)
     {}
 
     PassManager::~PassManager() noexcept
@@ -59,7 +59,7 @@ namespace vkcv
             m_Device.destroy(pass);
 
         m_RenderPasses.clear();
-        m_NextPassId = 1;
+        m_NextPassId = 0;
     }
 
     PassHandle PassManager::createPass(const PassConfig &config)
@@ -122,14 +122,14 @@ namespace vkcv
 
         vk::RenderPass vkObject{nullptr};
         if(m_Device.createRenderPass(&passInfo, nullptr, &vkObject) != vk::Result::eSuccess)
-            return PassHandle{0};
+            return PassHandle();
 
         m_RenderPasses.push_back(vkObject);
-            return PassHandle{m_NextPassId++};
+		return PassHandle(m_NextPassId++);
     }
 
     vk::RenderPass PassManager::getVkPass(const PassHandle &handle) const
     {
-        return m_RenderPasses[handle.id - 1];
+        return m_RenderPasses[handle.getId()];
     }
 }
diff --git a/src/vkcv/PipelineManager.cpp b/src/vkcv/PipelineManager.cpp
index 576cb47397a29184160423fdba6d1a09104a5566..c253aafd0438a64b2312fc4737d9b52d2b37f2e8 100644
--- a/src/vkcv/PipelineManager.cpp
+++ b/src/vkcv/PipelineManager.cpp
@@ -7,7 +7,7 @@ namespace vkcv
     m_Device{device},
     m_Pipelines{},
     m_PipelineLayouts{},
-    m_NextPipelineId{1}
+    m_NextPipelineId{0}
     {}
 
     PipelineManager::~PipelineManager() noexcept
@@ -20,7 +20,7 @@ namespace vkcv
 
         m_Pipelines.clear();
         m_PipelineLayouts.clear();
-        m_NextPipelineId = 1;
+        m_NextPipelineId = 0;
     }
 
     PipelineHandle PipelineManager::createPipeline(const PipelineConfig &config, const vk::RenderPass &pass)
@@ -30,7 +30,7 @@ namespace vkcv
         if (!(existsVertexShader && existsFragmentShader))
         {
             std::cout << "Core::createGraphicsPipeline requires vertex and fragment shader code" << std::endl;
-            return PipelineHandle{0};
+            return PipelineHandle();
         }
 
         // vertex shader stage
@@ -38,7 +38,7 @@ namespace vkcv
         vk::ShaderModuleCreateInfo vertexModuleInfo({}, vertexCode.size(), reinterpret_cast<uint32_t*>(vertexCode.data()));
         vk::ShaderModule vertexModule{};
         if (m_Device.createShaderModule(&vertexModuleInfo, nullptr, &vertexModule) != vk::Result::eSuccess)
-            return PipelineHandle{0};
+            return PipelineHandle();
 
         vk::PipelineShaderStageCreateInfo pipelineVertexShaderStageInfo(
                 {},
@@ -55,7 +55,7 @@ namespace vkcv
         if (m_Device.createShaderModule(&fragmentModuleInfo, nullptr, &fragmentModule) != vk::Result::eSuccess)
         {
             m_Device.destroy(vertexModule);
-            return PipelineHandle{0};
+            return PipelineHandle();
         }
 
         vk::PipelineShaderStageCreateInfo pipelineFragmentShaderStageInfo(
@@ -153,7 +153,7 @@ namespace vkcv
         {
             m_Device.destroy(vertexModule);
             m_Device.destroy(fragmentModule);
-            return PipelineHandle{0};
+            return PipelineHandle();
         }
 
         // graphics pipeline create
@@ -183,7 +183,7 @@ namespace vkcv
         {
             m_Device.destroy(vertexModule);
             m_Device.destroy(fragmentModule);
-            return PipelineHandle{0};
+            return PipelineHandle();
         }
 
         m_Device.destroy(vertexModule);
@@ -191,16 +191,16 @@ namespace vkcv
 
         m_Pipelines.push_back(vkPipeline);
         m_PipelineLayouts.push_back(vkPipelineLayout);
-        return PipelineHandle{m_NextPipelineId++};
+        return PipelineHandle(m_NextPipelineId++);
     }
 
     vk::Pipeline PipelineManager::getVkPipeline(const PipelineHandle &handle) const
     {
-        return m_Pipelines.at(handle.id -1);
+        return m_Pipelines.at(handle.getId());
     }
 
     vk::PipelineLayout PipelineManager::getVkPipelineLayout(const PipelineHandle &handle) const
     {
-        return m_PipelineLayouts.at(handle.id - 1);
+        return m_PipelineLayouts.at(handle.getId());
     }
 }
\ No newline at end of file