【问题标题】:vector<class> with private constructor带有私有构造函数的向量<class>
【发布时间】:2022-01-22 20:08:37
【问题描述】:

我有以下情况:一个类 NoEntry 包含可以被外部世界检查的数据,但是外部世界无论如何都不允许创建这些对象。这样的类看起来像这样:

#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

使用 NoEntry 对象是某些类的特权,被声明为 NoEntry 的朋友。所以类包含友元声明:

#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

我设计了如下PrivilegedOne接口:

#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

它的成员nextEntry很简单:它从文件中读取数据,并返回一个NoEntry对象。

//#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;
}

PrivilegedOne 的构造函数必须读取所有 NoEntry 对象,并且必须将它们存储在 d_noEntry 中。这是它的原始实现:

//#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
    }
}

“不工作”注释是导致所有问题的行。

为什么声明没有发挥作用? 不修改 NoEntry 类中的任何内容,而只关注 PrivilegedOne:必须做什么才能允许此类的对象将 NoEntry 对象存储在其 d_noEntry 向量中?

我认为我应该重新设计 d_noEntry 的定义。然后我只需要修改带有“不工作”注释的行。

但我不确定如何。

【问题讨论】:

  • “不工作”到底是什么意思?你是在编译时还是运行时出错?
  • 它不起作用,因为向量不允许创建自己的元素类型的实例。
  • friend class std::vector&lt;NoEntry&gt;; 和一条评论请求您的同事原谅这种不合适的设计。
  • “导致所有问题”是什么意思?
  • @YSC 无法保证它会起作用。尝试创建向量元素的函数不一定是std::vector 的成员。该作业可以委托给不同的类或独立功能。它可能与一个标准库实现一起工作并与另一个标准库实现中断。

标签: c++ vector containers private


【解决方案1】:

作为@"n. 1.8e9-where's-my-share m."评论说,您不能创建适用于任何可能实现的这种类型的 std::vector。

push_back() method of std::vector 接受引用,但需要将对象复制到新位置。

您可以做的是创建一个包装器并 friend class 它。
像这样实现该类:

#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; }
};

修改NoEntry实现:

// 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
}

并在.cpp文件中定义新添加的构造器

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

同时修改PrivilegedOne类:
std::vector&lt;NoEntry&gt; d_noEntry; 替换为std::vector&lt;NoEntryWrapper&gt; d_noEntry;

你现在可以像你一样使用向量了。

示例代码:

        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

【讨论】:

    【解决方案2】:

    为什么语句没有发挥作用?

    因为NoEntry 不满足CopyInsertable requirements of push_back,这意味着vector::push_back 至少需要一个用于存储项目的公共复制构造函数。 push_back 需要将项目复制到内部存储阵列中,并在重新分配存储时再次复制。

    解决此限制的一种方法不是在向量中存储NoEntry 对象,而是指针或std::unique_ptr&lt;NoEntry&gt; 项。后者只有MoveConstructible and MoveAssignable requirements,没有CopyInsertable

    【讨论】:

    • 但是使用指针向量是行不通的,因为 noEntry 对象是临时的,所以向量将添加一个指向 undefined 的指针
    • @programmer2:很明显,为了简单起见,OP 的示例是不完整的,d_noEntry 在实际代码中的生命周期会更长。
    猜你喜欢
    • 1970-01-01
    • 2014-06-11
    • 1970-01-01
    • 1970-01-01
    • 2011-03-27
    • 2014-11-10
    • 2011-04-20
    • 1970-01-01
    • 2011-08-22
    相关资源
    最近更新 更多