Use of unique_ptr for class members
There is nothing technically wrong with your use of std::unique_ptr
here, but it's totally unnecessary in this situation. std::vector
is already essentially a unique pointer to an underlying array. Instead of having your class have a std::unique_ptr<std::vector<char>>
member it should likely just have a std::vector<char>
member.
I would also question why load_image
and save_image
are static if they need references/pointers to the class's members (or the object itself, in the case of save_image
). It seems like it would make much more sense for them to be non-static member functions. That way they would implicitly have access to the members of the object on which they're called.
There's also a potential memory leak in your load_image
function. You dynamically allocate the array pointed to by skip
. If any of the read operations between there and when you delete skip
were to throw an exception you would leak that array. 256 bytes may also not be enough to read the entire color map. You should probably use a std::vector<char>
. Better yet, rather than reading the values you don't want, you could just seek past them:
// Skip the ID String
file_stream.seekg(header.id_length, std::ios::cur);
// Skip the colour map if it doesn't exist
if (!(tgaDesc & 4)) {
int colourMapSize = header.colour_map_type * header.num_entries;
file_stream.seekg(colourMapSize, std::ios::cur);
}
Writing that example made me notice that tgaDesc
is always 0
, so that if
block will always run. Did you mean to check header.colour_map_type
here? Of course, if there's no color map then header.num_entries
should be 0, so I'm not sure the if
is even needed at all.
While we're in load_image
, you pass the std::ios::ate
flag when opening file_stream
but then immediately seekg
back to the beginning of the file. If you remove the std::ios::ate
flag then the stream will initially be positioned at the beginning of the file and the extra seekg
could be eliminated.
The way you read the file header is mostly fine. Byte ordering (AKA endianness) could be a possible issue, but both TGA and most modern CPUs use little-endian byte order so you're probably fine unless you want to support some esoteric platform.
Putting that all together would give you an image
class that looks something like this:
class image {
public:
TGA_HEADER header;
std::vector<char> pixel_data;
image(const std::string& image_path);
~image();
void save_image(const std::string& file_name);
void load_image(const std::string& path);
};
image::image(const std::string& image_path)
{
load_image(image_path);
}
void image::load_image(const std::string& path)
{
std::ifstream file_stream(path, std::ios::in | std::ios::binary);
if (file_stream.is_open()) {
int tgaDesc = 0;
/* read header */
file_stream.read(&header.id_length, sizeof(header.id_length));
file_stream.read(&header.colour_map_type, sizeof(header.colour_map_type));
file_stream.read(&header.image_type, sizeof(header.image_type));
file_stream.read((char*)(&header.first_entry), sizeof(header.first_entry));
file_stream.read((char*)(&header.num_entries), sizeof(header.num_entries));
file_stream.read(&header.bits_per_entry, sizeof(header.bits_per_entry));
file_stream.read((char*)(&header.x_origin), sizeof(header.x_origin));
file_stream.read((char*)(&header.y_origin), sizeof(header.y_origin));
file_stream.read((char*)(&header.width), sizeof(header.width));
file_stream.read((char*)(&header.height), sizeof(header.height));
file_stream.read(&header.bits_per_pixel, sizeof(header.bits_per_pixel));
file_stream.read(&header.descriptor, sizeof(header.descriptor));
// Skip the ID String
file_stream.seekg(header.id_length, std::ios::cur);
// Skip the colour map if it doesn't exist
if (!(tgaDesc & 4)) {
int colourMapSize = header.colour_map_type * header.num_entries;
file_stream.seekg(colourMapSize, std::ios::cur);
}
int imageDataSize = header.width * header.height * (header.bits_per_pixel / 8);
pixel_data.resize(imageDataSize);
/* read image data */
file_stream.read(pixel_data.data(), imageDataSize);
}
}
How to define the operator= for a unique_ptr wrapper class?
Turns out, as @che.wang pointed out, I actually needed a Converting Assignment Constructor based on (2) from the unique_ptr reference like:
template<typename U> Unique(Unique<U>&& other) { pointer = std::move(other.pointer); }
unique_ptr and forward declaration: the proper way to code a factory function
When the compiler instantiates the destructor of std::unique_ptr<Foo>
, the compiler must find Foo::~Foo()
and call it. This means that Foo
must be a complete type at the point where std::unique_ptr<Foo>
is destroyed.
This code is fine:
struct Foo;
std::unique_ptr<Foo> create();
...as long as you don't need to call the destructor of std::unique_ptr<Foo>
! For a factory function that returns an std::unique_ptr
to a class, that class needs to be a complete type. This is how you would declare the factory:
#include "foo.hpp"
std::unique_ptr<Foo> create();
You seem to be implementing pimpl with an std::unique_ptr
correctly. You must define A::~A()
at the point where B
is complete (which is in the cpp file). You must define A::A()
in the same place because B
must be complete if you want to allocate memory and call its constructor.
So this is fine:
// a.hpp
struct A {
A();
~A();
private:
struct B;
std::unique_ptr<B> b;
};
// a.cpp
struct A::B {
// ...
};
A::A()
: b{std::make_unique<B>()} {}
A::~A() = default;
Now let's consider this (we'll pretend that I didn't make b
private):
int main() {
A a;
auto b = std::move(a.b);
}
What exactly is going on here?
- We are move constructing a
std::unique_ptr<B>
to initializeb
. b
is a local variable which means that its destructor will be called at the end of the scope.B
must be a complete type when the destructor forstd::unique_ptr<B>
is instantiated.B
is an incomplete type so we cannot destroyb
.
Ok, so you cannot pass around an std::unique_ptr<B>
if B
is an incomplete type. This restriction makes sense. pimpl means "pointer to implementation". It doesn't make sense for external code to access the implementation of A
so A::b
should be private. If you must access A::b
then this is not pimpl, this is something else.
If you really must access A::b
while keeping the definition of B
hidden then there are a few workarounds.
std::shared_ptr<B>
. This deletes the object polymorphically so that B
doesn't need to be a complete type when the destructor of std::shared_ptr<B>
is instantiated. It's not quite as fast as std::unique_ptr<B>
and I personally prefer to avoid std::shared_ptr
unless absolutely necessary.
std::unique_ptr<B, void(*)(B *)>
. Similar to the way that std::shared_ptr<B>
deletes the object. A function pointer is passed on construction that is responsible for deleting. This has the overhead of carrying around a function pointer unnecessarily.
std::unique_ptr<B, DeleteB>
. The fastest possible solution. However, it's probably a little bit annoying if you have more than a handful of pimpl (but not really pimpl) classes because you can't define a template. This is how you would do it:
// a.hpp
struct DeleteB {
void operator()(B *) const noexcept;
};
// a.cpp
void DeleteB::operator()(B *b) const noexcept {
delete b;
}
Defining a custom deleter is probably the best option but if I were you, I'd find a way to avoid needing to access implementation details from outside the class.
What happens to a pointer after it has become a unique_ptr?
Wrapping a raw pointer inside of a smart pointer does not change anything about the raw pointer. It still points at the same memory it was originally pointing at. The smart pointer is merely copying the pointer and then managing the pointed-at memory for you, delete
'ing the memory when the smart pointer is destroyed or is otherwise assigned a different pointer.
FYI, it would be more safer and more efficient to use std::make_unique()
instead of new
, eg:
template<typename T, typename... TArgs>
void addComponent(TArgs&&... MArgs) {
if (!components.count(typeid(T))) {
auto c = std::make_unique<T>(std::forward<TArgs>(MArgs)...);
c->entity = this;
c->init();
components[typeid(T)] = std::move(c);
}
}
What is the syntax to declare a unique_Ptr variable in a header, then assign it later in the constructor?
The following should work fine:
spriteBatch = std::unique_ptr<SpriteBatch>(new SpriteBatch(m_d3dContext.Get()));
Alternatively, you can avoid repeating the type name with some make_unique
function.
spriteBatch = make_unique<SpriteBatch>(m_d3dContext.Get());
There's also the reset
member:
spriteBatch.reset(new SpriteBatch(m_d3dContext.Get()));
But, since you mention a constructor, why not just use the member initialization list?
CubeRenderer::CubeRenderer()
: spriteBatch(new SpriteBatch(m_d3dContext.Get())) {}
Working with std::unique_ptr and std::queue
If I got your aim correctly, you definitely want
std::unique_ptr<MyObject::Packet> MyObject::FrontOfQueue()
{
auto rv = std::move(PacketQueue.front());
PacketQueue.pop();
return rv;
}
// ...
std::unique_ptr<MyObject::Packet> frame = object.FrontOfQueue();
Notice, no raw pointers are used.
I think it will automatically deque once the address out of scope.
This assumption is wrong. Nothing is dequeued until .pop() is called.
Related Topics
How to Tokenize (Words) Classifying Punctuation as Space
Crtp -- Accessing Incomplete Type Members
Safety of Casting Between Pointers of Two Identical Classes
In C++, Can a Class with a Const Data Member Not Have a Copy Assignment Operator
Reasonably Portable Way to Get Top 64-Bits from 64X64 Bit Multiply
Can and Does the Compiler Optimize Out Two Atomic Loads
Locking Strategies and Techniques for Preventing Deadlocks in Code
Is There a Readable Implementation of the Stl
Passing a C++ Complex Array to C
Std::Cin Doesn't Throw an Exception on Bad Input
How to Set Given Channel of a Cv::Mat to a Given Value Efficiently Without Changing Other Channels
How to Convert Concatenated Strings to Wide-Char with the C Preprocessor
Com(C++) Programming Tutorials
C/C++ Inline Assembler with Instructions in String Variables