C++ 对象需要让函数访问其所有数据

C++ 对象需要让函数访问其所有数据,c++,design-patterns,encapsulation,C++,Design Patterns,Encapsulation,当正确使用封装和“告诉,不要问”原则时,应该没有理由向对象询问信息。 然而,我遇到了这样一种情况(让我知道这个设计本身是否糟糕),我有一个对象,其中一个成员变量指向类外的函数 在我的应用程序的某个点上,我的对象需要调用函数,然后函数应该根据对象的状态进行操作 下面是一个示例类: typedef void(*fptr)(Foo*); class Foo { public: Foo(string name, fptr function); void activ

当正确使用封装和“告诉,不要问”原则时,应该没有理由向对象询问信息。 然而,我遇到了这样一种情况(让我知道这个设计本身是否糟糕),我有一个对象,其中一个成员变量指向类外的函数

在我的应用程序的某个点上,我的对象需要调用函数,然后函数应该根据对象的状态进行操作

下面是一个示例类:

typedef void(*fptr)(Foo*);
class Foo {
    public:
        Foo(string name, fptr function);
        void activate()
        {
            m_function(this);
        }
    private:
        string m_name;
        fptr m_function;
};
这就是类,现在开发人员可以像这样使用该类

void print(Foo *sender)
{
    cout << "Print works!" << endl;
}

int main(int argc, char **argv)
{
    Foo foo("My foo", &print);
    foo.activate();
    // output: "Print works!"
}
但这与不使用封装并没有太大区别,而且它似乎也不是一个太好的解决方案。
解决这个问题的最佳方法是什么?

从外部看,私有变量不存在,因此开发人员不可能“想要”打印它们

如果他们确实想要,那么类成员(或者更好,类中返回其内容的查询)应该是公共的,函数应该是类的成员,或者在特定情况下可以使用一些
朋友
机制


总之,不要开始破坏封装——相反,重新考虑封装背后的抽象,如果需要,为类的属性创建新的查询,这些属性在设计类的时候是没有用的,但现在是。

我会让
激活()
一个抽象方法和所有类的属性都受到保护。 此外,不需要使用
fptr

class Foo {
public:
    Foo(string name);
    virtual void activate() = 0;
protected:
    string m_name;
};
现在,当有人想要使用您的类时,他只是从中继承了自己的类:

class MyFoo : public Foo {
public:
    MyFoo(string name);
    virtual void activate()
    {
        cout << m_name << " says: Hello World!" << endl;
    }
};

int main(int argc, char **argv)
{
    MyFoo foo("My foo");
    foo.activate();
    // output: "My Foo says: Hello World!"
}

让我们面对它,C++访问控制是在考虑到一些用例的情况下设计的,并且通常是可用的,但从来没有声明要覆盖所有的东西。如果您不能仅使用private和friend来解决这种情况,并且必须允许任意函数访问内部,那么最好的方法是将它们公开并继续

二传手肯定不会推动你前进,只是徒劳地增加复杂性。如果数据是有效的,不要假装它不是,试图掩盖事实


寻找根本原因——为什么外部需要您的成员并重新安排。

如果函数应该能够看到字符串,那么您可能希望将函数参数类型更改为
常量字符串&
,但外部世界的其他人将看不到它。您也可以考虑使用<代码> STD::函数< /C> >代替您的函数类型。这有两个基本的优点:可以将闭包(也称为lambdas)传递给构造函数,并且可以更轻松地读取它。编辑的代码如下所示:

class Foo {
    public:
        template <typename F>
        Foo(string name, F && function) 
            : m_name    (std::move(name))
            , m_function(std::forward<F>(function))
        {
        }

        void activate()
        {
            m_function(m_name);
        }
    private:
        string m_name;
        std::function<void(const string &)> m_function;
};
class-Foo{
公众:
模板
Foo(字符串名称、F&函数)
:m_名称(标准::移动(名称))
,m_函数(标准::前进(函数))
{
}
无效激活()
{
m_函数(m_名称);
}
私人:
字符串m_name;
std::函数m_函数;
};
客户端代码如下所示

int main(int argc, char **argv)
{
    Foo foo("My foo", [](const string & s){ cout << s << endl; });
    foo.activate();
    // output: "My foo"
}
int main(int argc,char**argv)
{
Foo-Foo(“我的Foo”,[](const-string&s){cout您要问的是“我如何保持我的成员的隐私,但仍然以某种方式访问它们?”

从这个角度来看,您的
struct FooBar
解决方案实际上相当合理。唯一的问题是它有点效率低下。您最好传递
const FooBar&
而不是按值传递
FooBar

您的
struct FooBar
解决方案甚至比分部类更好,因为您可以精确地指定回调应该访问哪些成员

编辑:更仔细地阅读您的
struct FooBar
解决方案,我发现您正在考虑在将成员传递到回调之前逐个复制成员。您可以通过在
Foo
类中放置
FooBar
对象跳过所有这些,如下所示:

struct FooBar {
    string name;
};

typedef void(*fptr)(const FooBar&);

class Foo {
public:
    Foo(string name, fptr function);
    void activate()
    {
        m_function(data);
    }
private:
    FooBar data;
    fptr m_function;
};
值得指出的是,使用此解决方案,回调无法访问m_函数,除非您决定将其放入
FooBar
。这就是我所说的,您可以指定回调应该访问哪些成员

我可以忽略封装,为name和函数将来可能需要的所有其他属性创建setter和getter。这是一个非常糟糕的解决方案,我应该为类中的所有内容创建setter和getter,因为函数可以对我的对象做任何事

正确-这基本上是公开实现细节(在大多数情况下,不是您应该做的事情)

另一种解决方案是一个结构,其中包含所需的属性:

struct FooBar {
    string name;
};

typedef void(*fptr)(FooBar);

void Foo::activate()
{
    FooBar fb;
    fb.name = m_name;
    m_function(fb);
}
[…]但这与不使用封装没有太大区别,而且它似乎也不是一个太好的解决方案。解决这个问题的最佳方法是什么

实际上,它是非常不同的。考虑一下,你实际上调用了一个具有正常参数的外部函数:

struct EventData { string name, yadayada; }

class Foo
{
public:
    void activate()
    {
        m_function( EventData(m_name, yadayada) );
    }
};
这不是访问私有数据(Foo访问它自己的私有数据,m_函数访问它自己的参数值),而是依赖注入


这种方法没有架构上的折衷。

请参见。@JoachimPilborg我认为他试图避免使用getter和setter,而不是试图找到使用它们的理由。如果您只需要观察数据,那么使用getter和no setter似乎是最好的解决方案。您希望数据是公共的或私有的。能够访问任何人创建的函数中的数据都会使数据有效地公开。@JurajBlaho回调是在非常特定的情况下调用的,因此使成员只对回调可用是一种有用的访问控制形式。如果你试图滥用回调来“使数据有效地公开”,你就是在挖苦你
struct EventData { string name, yadayada; }

class Foo
{
public:
    void activate()
    {
        m_function( EventData(m_name, yadayada) );
    }
};