一个反复出现的常数

Mor*_*hai 9 c++

我经常发现自己必须定义一个函数的两个版本,以便有一个是const的,一个是非const的(通常是一个getter,但并不总是).两者的不同之处仅在于一个的输入和输出是const,而另一个的输入和输出是非const.功能的胆量 - 真正的工作,是IDENTICAL.

然而,为了保持正确性,我需要它们.作为一个简单的实际示例,请采取以下措施:

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;
}
Run Code Online (Sandbox Code Playgroud)

如你所见,他们做同样的事情.我可以选择用另一个使用更多的演员来定义一个,如果胆量 - 实际工作,则更为简单:

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));
}
Run Code Online (Sandbox Code Playgroud)

所以,我发现这非常繁琐和多余.但是如果我想编写const-correct代码,那么我要么必须提供上述两种代码,要么我必须使用const-casts丢弃我的"消费者代码"以解决仅定义一个或另一个的问题. .

这有更好的模式吗?您认为这个问题的"最佳"方法是什么:

  • 提供给定函数的两个副本 - const和非const版本
  • 或只是一个版本,然后要求该代码的消费者按照他们的方式进行演员表演?

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

并获得奖励积分:

  • 你觉得这是C++ const系统的不幸副产品吗?
  • 或者你觉得这等于触及奥林匹斯山的高度?

编辑:

如果我只提供第一个 - 接受const返回const,那么任何需要修改返回项目的消费者,或者将返回的项目交给另一个将修改它的函数,必须抛弃constness.

类似地,如果我只提供第二个定义 - 取非const并返回非const,那么具有const pidl的消费者必须抛弃constness才能使用上面的函数,老实说,这不会修改constness该项目本身.

也许更抽象是可取的:

THING & Foo(THING & it);
const THING & Foo(const THING & it);
Run Code Online (Sandbox Code Playgroud)

我很乐意有一个结构:

const_neutral THING & Foo(const_neutral THING & it);
Run Code Online (Sandbox Code Playgroud)

我当然可以这样做:

THING & Foo(const THING & it);
Run Code Online (Sandbox Code Playgroud)

但那总是以错误的方式揉搓我.我说的是"我不会修改你的内容,但我会在你的代码中摒弃你委托给我的常量."

现在,一个客户,它有:

const THING & it = GetAConstThing();
...
ModifyAThing(Foo(it));
Run Code Online (Sandbox Code Playgroud)

那是错的.GetAConstThing与调用者的契约是给它一个const引用.调用者不应该修改东西 - 只对其使用const操作.是的,调用者可能是邪恶的,错误的,抛弃了它的常量,但那只是邪恶(tm).

对我而言,问题的关键在于Foo是中立的.它实际上并没有修改它给定的东西,但它的输出需要传播其参数的常量.

注意:第二次编辑格式化.

Unc*_*ens 5

IMO这是const系统的一个不幸的副产品,但它并不经常出现:只有当函数或方法给出某些东西的指针/引用时(无论它们是否修改某些东西,函数都无法分发)它没有的权利或const-correctness会严重破坏,所以这些重载是不可避免的).

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

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
}
Run Code Online (Sandbox Code Playgroud)

  • 这是在现有实现中执行此操作的方法,但问题是当您需要执行强制转换来代替"比这更复杂的东西"时,如代码示例所示.在我们的例子中,如果`T`是一些`const U`,我们需要转换为`const char*`,如果`T`不是const,我们需要转换为`char*`.然后你会陷入TMP的混乱中去做. (2认同)

Pav*_*aev 3

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

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"
Run Code Online (Sandbox Code Playgroud)

现在你实际上可以使用模板元编程来完成这种事情,但这很快就会变得混乱:

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;
}
Run Code Online (Sandbox Code Playgroud)

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

或者,您可以使用模板化版本来重用 .cpp 文件中的代码,然后将其包装到 const/非常量对中并在标头中公开它。这样,您几乎只复制函数签名。