Search code examples
c++referencemoveunique-ptr

Unique_Ptr: Attemting To Reference A Deleted Function


So I'm working on a school project right now in C++, although I'm not too familiar with the language yet. The whole project is divided in several Milestones. 1: Reading a list with different Types of Creatures and storing them in a vector 2: Reading a TGA-File and storing it in a class. ... 5: Reading a TGA-Picture for every read Creature-Type and storing it for further use. (Printing on GUI, Remove/add)

So I thought it is a good idea to store the picture for each type in the class itself, as it should only be loaded once. The load() function in my TGAPicture class will return std::unique_ptr so I added the type as an argument in my CreatureType class. After doing that, I got several error like this:

Error   C2280   'biosim::CreatureType::CreatureType(const biosim::CreatureType &)': attempting to reference a deleted function  bio-sim-qt  E:\Development\C++\bio-sim-qt\bio-sim-qt\qtmain.cpp 58  1   

Error (active)      function "biosim::CreatureType::CreatureType(const biosim::CreatureType &)" (declared implicitly) cannot be referenced -- it is a deleted function  bio-sim-qt  e:\Development\C++\bio-sim-qt\bio-sim-qt\Model.cpp  15  26  

So I read about 10 questions with similar titles like mine and every one pointed out, that you cant copy unique_ptr and suggested solutions like using std::move() or returning a reference. Although I tried to use these to fix my problem, I wasn't able to do it in the slightest, probably because I'm pretty new to C++ and have never worked with unique pointers.

This is the code, that seems relevant to me:

/**
 * @class CreatureType
 * Object of the various CreatureTypes ingame
 */
class CreatureType {

    private:
    std::string name;
    int strengh;
    int speed;
    int lifespan;
    std::vector<std::string> attributes;
    std::string path;
    std::unique_ptr<TGAPicture> picture; //What I tried to add in order to stre my pictures

    public:
    CreatureType(const std::string& name
                , int strengh, int speed, int lifespan
                , const std::vector<std::string>& basic_strings
                , const std::string& path);

    /**
    * Initializes list with CreatureTypes by reading from a .txt-File
    */
    static CreatureList load(const std::string& file);

    /**
     * Printing Data in various ways
     */
    void getInfo() const;
    void getInfoInOneLine() const;

    std::string getName() const;
    int getStrengh() const;
    int getSpeed() const;
    int getLifespan() const;
    std::vector<std::string> getAttributes() const;
    std::string getPath() const;
};

}

CreatureType::CreatureType(const std::string& name
                            , int strengh, int speed, int lifespan
                            , const std::vector<std::string>& basic_strings
                            , const std::string& path)
    : name(name),
    strengh(strengh),
    speed(speed),
    lifespan(lifespan),
    attributes(basic_strings),
    path(path),
    picture(TGAPicture::loadPicture(Reference::PicturePath::creatureBasePath + path)){ }

/**
 * Implementation Notes:
 * - Does a line not fullfill the requirenments, it will be ignored
 * - @see <a href="https://elearning.uni-bayreuth.de/pluginfile.php/644828/mod_resource/content/2/Aufgabenblatt_1.pdf">Formation</a>
 * - Prints data with std::cout
 */
CreatureList CreatureType::load(const std::string& file) {
    CreatureList creatureList;
    std::ifstream fileStream; //Datei-Handle
    int lineNumber = 0;
    int correctLinesRead = 0;


    fileStream.open(file, std::ios::in);
    if (!fileStream.is_open()) {
        throw FileNotFoundException(file);
    }

    logger << INFO << "Einlesevorgang wird gestartet\n";

    //One line per loop
    while (!fileStream.eof()) {
        bool skipLine = false;

        std::string line;
        getline(fileStream, line);
        lineNumber++;

        ... //Checking if data is valid 


        //Every Parameter does exist and is valid
        creatureList.push_back(CreatureType(creatureArgs[0]
                                            , strengh, speed, lifespan
                                            , attributes, creatureArgs[5]));
        correctLinesRead++;
    }

return creatureList;
}

TGAPicture:

//no padding bytes
#pragma pack( push, 1 )
/**
 * @struct TGAHeader
 * Represents the standard TGA-Header.
 */
struct TGAHeader {
    char idLength;
    char colourmapType;
    char imagetype;

    short colourmapStart;
    short colourmapLength;
    char colourmapBits;

    short xOrigin;
    short yOrigin;
    short width;
    short height;
    char bits;
    char descriptor;

};
#pragma pack( pop )


/**
* @struct RGBA
* Represents a Pixel with a red, green, blue and possibly alpha value
*/
struct RGBA {
    std::uint8_t B, G, R, A;
};

/**
* @class TGAPicture
* Class used to represent TGA-Files, that are used in the program
*/
class TGAPicture {

    public:
    TGAPicture(const TGAPicture& other)
        : pixel(other.pixel),
        header(other.header),
        width(other.width),
        height(other.height),
        size(other.size),
        bitsPerPixel(other.bitsPerPixel) {}

    TGAPicture(TGAPicture&& other) noexcept
        : pixel(std::move(other.pixel)),
        header(std::move(other.header)),
        width(other.width),
        height(other.height),
        size(other.size),
        bitsPerPixel(other.bitsPerPixel) {}

    TGAPicture& operator=(const TGAPicture& other) {
        if (this == &other)
            return *this;
        pixel = other.pixel;
        header = other.header;
        width = other.width;
        height = other.height;
        size = other.size;
        bitsPerPixel = other.bitsPerPixel;
        return *this;
    }

    TGAPicture& operator=(TGAPicture&& other) noexcept {
        if (this == &other)
            return *this;
        pixel = std::move(other.pixel);
        header = std::move(other.header);
        width = other.width;
        height = other.height;
        size = other.size;
        bitsPerPixel = other.bitsPerPixel;
        return *this;
    }

    private:
    std::vector<RGBA> pixel; //Containes every pixel of the picture
    TGAHeader header;
    short width, height, size, bitsPerPixel;

    ...

    public:
    /**
    * Loads and initializes a picture to be used in the program
    * @throws TGAExpection if file could not be loaded
    */
    static std::unique_ptr<TGAPicture> loadPicture(const std::string& path);
    TGAPicture(const std::vector<RGBA>& pixel, const TGAHeader& header);
    ~TGAPicture();
    ....
};
}
#endif

cpp:

TGAPicture::TGAPicture(const std::vector<RGBA>& pixel, const TGAHeader& header)
    : pixel(pixel),
    header(header),
    width(header.width),
    height(header.height),
    size(header.width * header.height * (header.bits / 8)),
    bitsPerPixel(header.bits) { }



std::unique_ptr<TGAPicture> TGAPicture::loadPicture(const std::string& path) {
    ...


    for (int i = 0; i < header.height * header.width; i++) {
        pixel[i].B = *(bufferPosition++);
        pixel[i].G = *(bufferPosition++);
        pixel[i].R = *(bufferPosition++);
        pixel[i].A = (header.bits > 24 ? *(bufferPosition++) : 0xFF);
    }

    /**
     * Return unique_ptr
     * - ObjectFactory
     * - Automatic Deletion
     */
    return std::unique_ptr<TGAPicture>{new TGAPicture(pixel, header)};

}

And one class with an error would be:

class Model {

    public:
    explicit Model(const CreatureList& creatureList);
    ~Model();


    Terrain* getTerrain() const;
    CreatureList& getCreatureList();

    private:
    CreatureList creatureList;
    Terrain* terrain;

};


Model::Model(const CreatureList& creatureList) : creatureList(creatureList),
                                                terrain(new Terrain()) {

    for (CreatureType ty : creatureList) {  //line with errror
        ty.getInfoInOneLine();
    }
}

What do I need to change for it to work? And what would be the optimal way? Pretty sure that I'm supposed to use unique_ptr as return for the TGA::load() method. I hope you can see through this mess and I'd like to apologize if my English isn't perfect, since it's not my first langugage.


Solution

  • std::unique_ptr is not copyable. It wouldn't be unique anymore if you could copy it.

    You create copies of the elements in creatureList in your loop in Model's constructor, but they have a non-copyable member, and so are non-copyable themselves by default. If you don't actually need a copy of the elements, you should use references:

    Model::Model(const CreatureList& creatureList)
        : creatureList(creatureList),
          terrain(new Terrain())
    {
        for (CreatureType& ty : creatureList) {  // changed to reference instead
                                                 // Note: this is still working with
                                                 //       parameter, not the object's
                                                 //       member.
            ty.getInfoInOneLine();
        }
    }
    

    You haven't actually provided a definition for CreatureList, but I suspect it also isn't copyable. This means that Model::Model's argument can't be copied into the object's member. You have two options to fix this: make sure to move your CreatureList or make it copyable.

    std::unique_ptr is movable, which means that CreatureType is by default as well, so you can do something like this:

    Model::Model(CreatureList creatureList)  // Take by value now
        : creatureList(std::move(creatureList)),  // Move the parameter to the member
          terrain(new Terrain())
    {
        for (CreatureType& ty : this->creatureList) {  // Use this-> to access member
                                                       // instead of now moved-from
                                                       // parameter.  You could also
                                                       // just change them to have
                                                       // different names.
            ty.getInfoInOneLine();
        }
    }
    

    This changes Model's constructor to take its parameter by value and moves that value into the object's creatureList member.

    If it makes sense, you could also make CreatureType copyable by adding an explicit copy constructor that copys the object pointed to by picture:

    CreatureType::CreatureType(const CreatureType& other)
        : name(other.name),
          strengh(other.strengh),
          speed(other.speed),
          lifespan(other.lifespan),
          attributes(other.attributes),
          path(other.path),
          picture(new TGAPicture(*other.picture))
    {
    }
    

    If you do that, the implicit move constructor will no longer be generated by the compiler, so you'll want to define that yourself:

    CreatureType::CreatureType(CreatureType&& other)
        : name(std::move(other.name)),
          strengh(other.strengh),
          speed(other.speed),
          lifespan(other.lifespan),
          attributes(std::move(other.attributes)),
          path(std::move(other.path)),
          picture(std::move(other.picture))
    {
    }
    

    There doesn't seem to be any reason for TGAPicture::loadPicture to return a std::unique_ptr though. If you just return by value from that function you will avoid all of these problems:

    TGAPicture TGAPicture::loadPicture(const std::string& path) {
        // ...
        return TGAPicture{pixel, header};
    }