【问题标题】:Extending class functionality with methods after declaration声明后使用方法扩展类功能
【发布时间】:2014-07-29 03:42:04
【问题描述】:

我经常遇到想要实现数据结构的问题,并希望允许用户使用功能功能对其进行扩展;即向数据结构添加功能但不添加字节。一个例子可以是使用 sum 方法扩展 std::vector:

#include <iostream>

#include <vector>

// for the header file
template<>
int std::vector<int>::sum();

//for the object file
template<>
int std::vector<int>::sum() {
    int s=0;
    for(auto v = this->begin(); v!=this->end(); ++v) s+=*v;
    return s;
}


int main() {
    std::vector<int> numbers;
    numbers.push_back(5);
    numbers.push_back(2);
    numbers.push_back(6);
    numbers.push_back(9);

    std::cout << numbers.sum() << std::endl;

    return 0;
}

见:http://ideone.com/YyWs5r

所以这是非法的,因为不能向这样的类添加函数。显然这是 c++(11) 的一些设计决定。它可以通过两种方式规避,即定义一个

int sum(std::vector<int> &v) { ... }

这就是 std::sort 的工作方式,所以我想这是 c++(11) 的预期方式。据我所知,我认为这是在 C++ 中做到这一点的最佳方式。但是,它不允许我访问 std::vector 的私有属性。假设以(某种)方法访问私有属性是公平的,也许我是邪恶的。但是,通常我希望我的类的用户不要访问某些东西,但是希望允许我的扩展程序访问它们。例如,我可以想象 std::sort 可以 w.r.t 进行优化。具体的容器实现知识和访问权限。

另一种方式是继承std::vector,但我发现这是不可接受的,原因如下:

  • 如果两方已经使用方法扩展了类,其中一方想要使用,那么一方需要从一个子类转换为另一个子类。这是可笑的,因为将数据转换为数据而不实际更改字节,因为两个子类(可以)具有完全相同的内存实现和分段。另请注意,数据转换通常是样板代码,样板代码应被视为邪恶。
  • 一个是不必要地将功能与数据结构混合在一起,例如,类名 sum_vector 或 mean_vector 完全是。

作为一个简短的提醒,我不是在寻找像“你不能在 c++ 中那样做”这样的答案,我已经知道了 (Add a method to existing C++ class in other file)。 不过,我想知道是否有一种很好的方法来进行功能类扩展。我应该如何管理访问私有字段?我想要私有字段访问是不合理的原因是什么?为什么我不能区分 extenderuser 访问?

注意:可以说扩展器需要受保护的访问权限,而用户需要公共访问权限,但是,就像我说的,那将是扩展的继承方式,出于上述原因,我非常不喜欢它。

【问题讨论】:

  • 必读:GotW #84
  • 请不要!方法是一件很糟糕的事情。这些应该只是将结构作为第一个参数传递的语法糖。因为它们不是,你应该尽可能避免使用方法。
  • @polkovnikov.ph :我同意,我更喜欢函数而不是方法。但是,我是否应该通过将字段公开或添加公共访问方法来尽可能公开数据访问内容?
  • C# 改进的扩展方法,如果 C++ 委员会将来添加类似的东西,我会很高兴。

标签: c++ function class c++11 methods


【解决方案1】:

您永远不应该访问标准容器的私有成员,因为它们不是其接口的一部分

但是,您已经可以扩展标准容器的功能,例如std::vector:即通过明智地使用迭代器和标准算法。

例如sum 功能由使用std::vectorbegin()end() 功能的非成员函数提供

#include <algorithm>
#include <iterator>
#include <vector>

template<class Container, class Ret = decltype(*begin(c))>
Ret sum(Container const& c)
{
    return std::accumulate(begin(c), end(c), Ret{});
}

【讨论】:

  • 这里你依赖于这样一个事实,即可以有效地访问 std::vector 的数据结构以使用迭代器计算总和;这对于每个数据结构来说都不容易。考虑一个图数据结构,其中隐藏了实际的边和节点列表;允许用户只创建一个图形对象。另一方面,扩展程序知道图表,他应该有权访问这些列表;为用户提供 Dijkstra 或原始类设计者没有(想要)考虑的其他功能。
  • @Herbert 隐藏其边和节点的通用图是一种非常糟糕的数据结构,除非它用作实现细节,例如在std::map .看看Boost.Graph 例如与标准库一样可扩展的最先进的 C++ 图形接口。
  • @Herbert C++ 标准库的一般理念是M 容器和N 算法(通过迭代器连接)的分离允许M+N instead of M*N code complexity。这是一种非常有效的方法,您应该努力模仿。
  • 我的具体图结构是时间表数据的时间相关图(后来是时间扩展图);据我所知和 googleability 此类库尚不存在。但是,我认为我明白你关于函数/数据结构分离的观点。
  • 为什么只限于向量? template &lt;class C&gt; auto sum(C const&amp; c) -&gt; C::value_type ...
【解决方案2】:

考虑这样的事情:

#include <iostream>

class Foo{
    int a;
public:
    Foo(int a){this->a = a;}
    int getA(){return this->a;}
    void * extendedMethod(void *(*func)(int, char **, Foo*), int argc, char **argv){
        return func(argc, argv, this);
    }

};

void * extendFooWith(int argc, char **argv, Foo* self){
    /* You can call methods on self... but still no access to private fields */
    std::cout << self->getA();
    return self;
}

int main(int argc, char const *argv[])
{
    Foo foo(5);
    foo.extendedMethod(extendFooWith, 0 /*argc*/, NULL /*argv*/);
    return 0;
}

这是我认为用方法扩展类的最佳方式。访问私有字段的唯一方法是从extendedMethod() 内部,即可能出现这样的情况:return func(this-&gt;a, argv, this);,但它不再那么通用了。改进它的一种方法是在extendedMethod() 内部检查传递了什么样的指针,并根据它访问您感兴趣的私有字段并将它们传递给func(),但这需要为每个extendedMethod() 添加代码您将使用其他方法扩展您的课程。

【讨论】:

  • 这将在运行时扩展函数,并包含样板代码,不过谢谢:) 此外,我认为这不是 c++ 做事的方式。
  • @Herbert 是的,但函数指针不是一个坏习惯;)
  • 函数指针+样板代码是;为了支持语法糖,您的解决方案(根据我的口味)非常接近于用 C++ 编写解释器。
  • 一般来说,我尽量避免在 c++ 中使用指针,如果(人为)可能的话,我会使用仿函数。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-27
  • 2012-09-10
  • 2017-02-12
相关资源
最近更新 更多