首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >一种反复出现的连体

一种反复出现的连体
EN

Stack Overflow用户
提问于 2009-11-30 21:13:12
回答 11查看 649关注 0票数 9

我经常发现自己必须定义一个函数的两个版本,以便有一个是const,另一个是非const(通常是getter,但并不总是这样)。两者的变化仅仅取决于一个是const的输入和输出,另一个是非const的输入和输出。功能的核心--真正的工作--是相同的。

但是,为了正确起见,我要两者兼得。作为一个简单的实际例子,以以下为例:

代码语言:javascript
复制
inline const ITEMIDLIST * GetNextItem(const ITEMIDLIST * pidl)
{
    return pidl ? reinterpret_cast<const ITEMIDLIST *>(reinterpret_cast<const BYTE *>(pidl) + pidl->mkid.cb) : NULL;
}

inline ITEMIDLIST * GetNextItem(ITEMIDLIST * pidl)
{
    return pidl ? reinterpret_cast<ITEMIDLIST *>(reinterpret_cast<BYTE *>(pidl) + pidl->mkid.cb) : NULL;
}

正如你所看到的,他们也在做同样的事情。我可以选择用更多的数据类型来定义另一种,如果真正的工作不那么琐碎的话,就更合适了:

代码语言:javascript
复制
inline const ITEMIDLIST * GetNextItem(const ITEMIDLIST * pidl)
{
    return pidl ? reinterpret_cast<const ITEMIDLIST *>(reinterpret_cast<const BYTE *>(pidl) + pidl->mkid.cb) : NULL;
}

inline ITEMIDLIST * GetNextItem(ITEMIDLIST * pidl)
{
    return const_cast<ITEMIDLIST *>(GetNextItem(const_cast<const ITEMIDLIST *>(pidl));
}

所以,我觉得这是非常乏味和多余的。但是,如果我想编写正确的代码,那么我要么必须提供上述两种代码,要么我必须用const-cast乱扔我的“消费者代码”,以避免只定义其中一个的问题。

有更好的模式吗?你认为解决这一问题的“最佳”办法是什么:

  • 提供给定函数的两个副本??const和non版本。
  • 或者只是一个版本,然后要求该代码的使用者按照他们的意愿进行强制转换?

还是有更好的方法来解决这个问题呢?是否正在对语言本身进行工作以完全缓解或消除这一问题?

至于分红:

  • 您是否认为这是C++ const-system的一个不幸的副产品?
  • 或者你觉得这相当于攀登奥林匹斯山的高度?

编辑:

如果我只提供第一个获取const返回const,那么任何需要修改返回项或将返回项传递给将修改它的另一个函数的使用者都必须放弃不变。

类似地,如果我只提供第二个定义-获取非const并返回non-const,那么具有const pidl的消费者必须丢弃该常数,以便使用上面的函数,这并不会改变项目本身的一致性。

也许需要更多的抽象:

代码语言:javascript
复制
THING & Foo(THING & it);
const THING & Foo(const THING & it);

我想要一个构造:

代码语言:javascript
复制
const_neutral THING & Foo(const_neutral THING & it);

我当然可以这样做:

代码语言:javascript
复制
THING & Foo(const THING & it);

但这总是让我觉得不对。我是说,“我不会修改你的东西的内容,但我要摆脱你在代码中默默地赋予我的坚定性。”

现在是一个客户,它有:

代码语言:javascript
复制
const THING & it = GetAConstThing();
...
ModifyAThing(Foo(it));

那是不对的。GetAConstThing与调用方的合同是给它一个const引用。要求调用方不要对其进行修改--仅使用const操作。是的,打电话的人可能是邪恶的和错误的,抛弃了它的不变,但那只是邪恶(Tm)。

对我来说,问题的症结是福是中立的。它实际上并没有修改给定的东西,但是它的输出需要传播它的参数的一致性。

注:第二次编辑格式。

EN

回答 11

Stack Overflow用户

回答已采纳

发布于 2009-11-30 21:27:14

我不认为这是const-正确性本身的缺陷,而是缺乏在cv-限定符上泛化方法的方便能力(同样,我们可以通过模板对类型进行泛化)。假设一下,想象一下如果你能写出这样的东西:

代码语言:javascript
复制
template<cvqual CV>
inline CV ITEMIDLIST* GetNextItem(CV ITEMIDLIST * pidl)
{
    return pidl ? reinterpret_cast<CV ITEMIDLIST *>(reinterpret_cast<CV BYTE *>(pidl) + pidl->mkid.cb) : NULL;
}

ITEMIDLIST o;
const ITEMIDLIST co;


ITEMIDLIST* po = GetNextItem(&o); // CV is deduced to be nothing
ITEMIDLIST* pco = GetNextItem(&co); // CV is deduced to be "const"

现在,您实际上可以使用模板元编程来完成这类工作,但是这很快就会变得很混乱:

代码语言:javascript
复制
template<class T, class TProto>
struct make_same_cv_as {
    typedef T result;
};

template<class T, class TProto>
struct make_same_cv_as<T, const TProto> {
    typedef const T result;
};

template<class T, class TProto>
struct make_same_cv_as<T, volatile TProto> {
    typedef volatile T result;
};

template<class T, class TProto>
struct make_same_cv_as<T, const volatile TProto> {
    typedef const volatile T result;
};

template<class CV_ITEMIDLIST>
inline CV_ITEMIDLIST* GetNextItem(CV_ITEMIDLIST* pidl)
{
    return pidl ? reinterpret_cast<CV_ITEMIDLIST*>(reinterpret_cast<typename make_same_cv_as<BYTE, CV_ITEMIDLIST>::result*>(pidl) + pidl->mkid.cb) : NULL;
}

上面的问题是所有模板的常见问题-它将允许您传递任何随机类型的对象,只要它有具有正确名称的成员,而不仅仅是ITEMIDLIST。当然,您可以使用各种“静态断言”实现,但这本身也是一种黑客行为。

或者,您可以使用模板版本来重用您的.cpp文件中的代码,然后将其包装成const/non对并在标头中公开。这样,您就只能复制函数签名了。

票数 3
EN

Stack Overflow用户

发布于 2009-11-30 21:28:08

IMO这是const系统的一个不幸的副产品,但它并不经常出现:只有当函数或方法给出指向某物的指针/引用时(不管它们是否修改了什么东西,函数就不能分发它没有的权限,或者const-正确性会严重破坏,因此这些重载是不可避免的)。

通常情况下,如果这些函数只是一个简短的行,我就会重复它们。如果实现更复杂,我使用模板来避免代码重复:

代码语言:javascript
复制
namespace
{
    //here T is intended to be either [int] or [const int]
    //basically you can also assert at compile-time 
    //whether the type is what it is supposed to be
    template <class T>
    T* do_foo(T* p)
    {
        return p; //suppose this is something more complicated than that
    }
}

int* foo(int* p)
{
    return do_foo(p);
}

const int* foo(const int* p)
{
    return do_foo(p);
}

int main()
{
    int* p = 0;
    const int* q = foo(p);  //non-const version
    foo(q);  //const version
}
票数 5
EN

Stack Overflow用户

发布于 2009-11-30 21:23:01

这里的真正问题似乎是,您为外部世界提供了(相对地)直接访问您类内部的权限。在少数情况下(例如,容器类),这是有意义的,但在大多数情况下,这意味着您作为哑数据提供对内部的低级别访问,您应该查看客户端代码对该数据所做的更高级别的操作,然后直接从您的类提供那些更高级别的操作。

编辑:虽然在这种情况下,显然没有涉及到任何类,但基本思想仍然是一样的。我不认为这也是逃避这个问题--我只是简单地指出,虽然我同意这是一个问题,但它只是很少出现。

我也不确定低级别的代码是否有理由这样做。我的大部分代码比大多数人使用的代码都低得多,而且我仍然很少遇到它。

Edit2:我还应该提到,C++ 0x有一个auto关键字的新定义,以及一个新关键字(decltype),这使得相当数量的事情更容易处理。我还没有尝试用它们实现这个确切的函数,但是这种一般的情况正是它们的目的(例如,根据传递的参数自动计算返回类型)。也就是说,他们通常做的比你想做的要多一点,所以在这种情况下,他们可能会有点笨拙(如果有用的话)。

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/1822429

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档