Home > database >  vector<class> with private constructor
vector<class> with private constructor

Time:12-22

I have the following situation: a class NoEntry contains data which can be inspected by the outside world, but the outside world is not allowed, in any way, to create those objects. Such a class looks like this:

#ifndef INCLUDED_NOENTRY_
#define INCLUDED_NOENTRY_

#include <string>

class NoEntry
{
    std::string d_name;
    size_t d_area = 0;
    size_t d_date = 0;

    public:
        std::string const &name() const;
        size_t area() const;
        size_t date() const;

    private:
        NoEntry(NoEntry const &other) = default;
        NoEntry() = default;
        NoEntry(std::string const &name, size_t area, size_t date);
};

#endif

Using NoEntry objects is a privilege of certain classes, declared as friends of NoEntry. So the class contains friend declarations:

#ifndef INCLUDED_NOENTRY_
#define INCLUDED_NOENTRY_

#include <string>

class NoEntry
{
    friend class PrivilegedOne;
    friend class PrivilegedTwo;

    std::string d_name;
    size_t d_area = 0;
    size_t d_date = 0;

    public:
        std::string const &name() const;
        size_t area() const;
        size_t date() const;

    private:
        NoEntry(NoEntry const &other) = default;
        NoEntry() = default;
        NoEntry(std::string const &name, size_t area, size_t date);
};

#endif

I have designed the following PrivilegedOne interface:

#ifndef INCLUDED_PRIVILEGEDONE_
#define INCLUDED_PRIVILEGEDONE_

#include <iosfwd>
#include <vector>

#include "../noentry/noentry.h"

class PrivilegedOne
{
    std::vector<NoEntry> d_noEntry;

    public:
        PrivilegedOne(std::string const &fname);

    private:
        NoEntry nextEntry(std::istream &in);    // empty name: all were read
};

#endif

Its member nextEntry is a simple one: it reads the data from file, and returns a NoEntry object.

//#define XERR
#include "privilegedone.ih"

NoEntry PrivilegedOne::nextEntry(istream &in)
{
    NoEntry ret;

    in >> ret.d_name >> ret.d_area >> ret.d_date;

    if (not in)                         // no more NoEntries: ensure
        ret.d_name.clear();             // that d_name is empty

    return ret;
}

PrvilegedOne's constructor must read all the NoEntry objects and must store them in d_noEntry. Here is its original implementation:

//#define XERR
#include "privilegedone.ih"

PrivilegedOne::PrivilegedOne(string const &fname)
{
    ifstream in{ fname };

    while (true)
    {
        NoEntry next = nextEntry(in); 

        if (next.name().empty())
            break;

        d_noEntry.push_back(next);          // Not working
    }
}

The "Not working" comment is the line that is causing all the problems.

Why doesn't the statement do its job? Without modifying anything in the class NoEntry, but merely concentrating on PrivilegedOne: what must be done to allow objects of this class to store NoEntry objects in its d_noEntry vector?

I think I should redesign the definition of d_noEntry. And then I'll only have to modify the line with the "not working" comment.

But I am not sure how.

CodePudding user response:

As @"n. 1.8e9-where's-my-share m." commented, you can't create a std::vector of this type that will work with any possible implementation.

The push_back() method of std::vector takes a reference, but needs to copy the object to the new location.

What you can do is to create a wrapper and friend class it.
Implement that class like:

#pragma once
#include "NoEntry.h"

class NoEntryWrapper {
    NoEntry content;
    friend class NoEntry;
public:
    NoEntryWrapper() = default;
    NoEntryWrapper(const NoEntryWrapper &) = default;
    NoEntryWrapper(const NoEntry & content) : content(content) {}
    NoEntry &getContent() { return content; }
};

Modify NoEntry implementation:

// do not include "NoEntryWrapper.h"
class NoEntryWrapper; // forward declare it

class NoEntry
{
    friend class PrivilegedOne;
    friend class PrivilegedTwo;
    friend class NoEntryWrapper; // this is new

    // ... your original code ...

    NoEntry(const NoEntryWrapper &wrapper);
    // make NoEntryWrapper implicitly convertable back to NoEntry
}

and define the newly added contructor in the .cpp file

#include "NoEntryWrapper.h"
NoEntry::NoEntry(const NoEntryWrapper &wrapper) : NoEntry(wrapper.content) {}

And also modify PrivilegedOne class:
Replace std::vector<NoEntry> d_noEntry; with std::vector<NoEntryWrapper> d_noEntry;.

You can use the vector now as you excepted.

Example code:

        std::vector<NoEntryWrapper> d_noEntry;
        d_noEntry.push_back(NoEntry("test", 0, 1));
        NoEntry test = d_noEntry[0];
        std::cout << test.d_name << " " << test.d_area << std::endl;

        d_noEntry[0] = NoEntry("other", 5, 5);
        d_noEntry[0].getContent().d_name = "other (name changed)";
        // this is the only drawback - you need a getter to access it this way
        NoEntry other = d_noEntry[0];
        std::cout << other.d_name << " " << other.d_area << std::endl;

// Output:
//test 0
//other (name changed) 5

CodePudding user response:

Why doesn't the statement do its job?

Because NoEntry does not meet the CopyInsertable requirements of push_back, which means, vector::push_back needs at least a public copy constructor for the items stored. push_back will need to copy items into the internal storage array, and copy again when it comes to reallocating storage.

One way of working around this limitation is not storing NoEntry objects in the vector, but pointers or std::unique_ptr<NoEntry> items. The latter has only MoveConstructible and MoveAssignable requirements, but not CopyInsertable.

  • Related