在返回副本而不是常量引用时,我会中断代码吗

Will I break code when returning a copy instead of a const reference

提问人:sbi 提问时间:11/12/2012 最后编辑:sbi 更新时间:11/12/2012 访问量:148

问:

我需要使用的框架定义了一个简单的互斥类,该类可以存储互斥锁所有者的名称,以帮助调试:

class mutex
{
public:
    explicit mutex(const std::string& mutex_owner = "");

    bool acquire() const;
    bool release() const;

    const std::string& get_name() const {return owner_name_;}

    // ...

private:
    std::string owner_name_;

    // ...
};

我刚刚更改了一些算法,使互斥类型成为模板参数,以便如果不需要锁定,出于性能原因,我可以传入此算法:

class non_mutex
{
public:
    explicit non_mutex(const std::string& mutex_owner = "")     {}

    bool acquire() const               {return true;}
    bool release() const               {return true;}

    std::string get_name() const {return "";}
};

由于这个函数不存储名称(无需调试),因此我将成员函数更改为返回 ,而不是 .get_name()std::stringconst std::string&

现在我的问题是:这(默默地会破坏任何东西吗?代码编译得很好,似乎也运行得很好,但这个代码库中的测试很少,而且这个函数大多只在出现问题时使用,而不是定期使用。

在什么情况下,此更改可能会触发运行时故障?

请注意,这是一个 C++03 环境,但我也对 C++11 的答案感兴趣。

C++语言

评论


答:

1赞 Šimon Tóth 11/12/2012 #1

好吧,您不再返回常量。您返回的是临时的。

从理论上讲,这可能会允许用户滥用返回值?也许?

顺便说一句。我会以这种方式解决问题:

static std::string empty_string;
const std::string& get_name() const { return empty_string; }

评论

0赞 Johannes Schaub - litb 11/12/2012
请注意,这具有不同的线程安全要求,因为多个互斥锁将返回相同的名称对象引用。
0赞 Šimon Tóth 11/12/2012
@JohannesSchaub升 这对恒定对象重要吗?
0赞 Steve Jessop 11/12/2012
@Let_Me_Be:可能应该是一个常量对象;-p。但我认为你是对的,只要没有人修改,就不应该有任何特定的线程问题。empty_stringempty_string
0赞 sbi 11/12/2012
没有冒犯的意思,但这非常模糊。前四句话中的三句话只是重复了我在问题中已经说过的话,第四句话暗示可能有什么东西,我想知道可能有什么。感谢您重新解决问题的提示,但我来这里问的原因正是因为我想知道这是否有必要。
1赞 Šimon Tóth 11/12/2012
@sbi 好吧,这就是为什么我们同时拥有赞成和反对按钮 :-)
4赞 Pubby 11/12/2012 #2

按值返回的那个可能会抛出错误的分配,引用的那个是 no-throw。所以这可能是一个问题。

此外,他们有可能调用不同的重载,并且特征会以不同的方式专门化,但我不会担心这一点。

评论

0赞 sbi 11/12/2012
分配确实可能是一个问题,如果不太可能的话。鉴于我们这里还没有右值引用,调用不同重载的潜力是什么?(我担心的正是那些有问题的孩子。这是一个小型设备,旨在 24/7 全天候运行。
0赞 Steve Jessop 11/12/2012
你可能会很幸运:如果你有一个小的字符串优化,那么我认为你的实现应该不会被抛弃。std::stringstd::non_mutex::get_name()
1赞 Steve Jessop 11/12/2012 #3

对于静默破损,一个区别是返回/返回所引用的对象的生存期。例如,请考虑以下代码:

const string &stupid_user(const string &s) { return s; }

const string &name = stupid_user(mtx.get_name());
mtx.acquire();
std::cout << name;

现在,如果 have type,则会在 .如果 have type,则它具有未定义的行为(在这种情况下,const 引用不会延长临时的生存期)。未定义的行为显然允许它通过您的测试。mtxmutexacquiremtxnon_mutex

对于不那么愚蠢的用户:

const string &name = mtx.get_name();
mtx.acquire();
std::cout << name;

现在的行为是打印新所有者,打印旧所有者。也许你的测试捕捉到了这一点,也许它们没有,但是如果调用代码假设了一个,并且你提供了一个类型,它是另一个类型,那么你就默默地破坏了调用代码。mutexnon_mutex

或者怎么样:

auto &&name = mtx.get_name();
mtx.acquire();
std::cout << name;

我认为这与非愚蠢的用户的行为相同,但我不确定。

如果你(或这个问题的未来访问者)对嘈杂的破损感兴趣,那么这取决于你如何定义允许使用的表达式,以便使用你的互斥概念(你希望你提出的两个类都满足)。

例如,如果允许表达式,则不满足概念的要求。&mtx.get_name()non_mutex

如果不允许该表达式,那么也许确实满足了要求 -- 仔细查看允许哪些表达式涉及对 的调用。如果你所需要的只是它的返回值是“可转换为”或类似的东西,那么你就没问题了。non_mutexget_namestring

如果您没有根据允许的表达式定义模板参数的要求,而是根据它所具有的成员函数签名和返回类型来定义,那么 (a) 您犯了一个错误,这不是基于模板的编译时多态性应该如何工作,并且 (b) 没有相同的成员函数签名和返回类型。non_mutex

评论

0赞 sbi 11/12/2012
当我“默默地”写时,我的意思是它不会导致任何编译错误,因为我并不担心这些错误。当编译失败时,可以修复依赖于返回值作为引用的代码。我担心代码可以编译,但可能会在运行时失败。因此,这种“默默地”甚至可能是指惊人的错误。(顺便说一句,我没有问如何修复依赖于结果作为引用的代码,我问这可能是什么样的代码。 从我这里举出的一个例子。+1
0赞 sbi 11/12/2012
P.S.:正如我(迟来的)在问题中写的那样,该平台没有 C++11 编译器,因此我们无法从 中受益。auto
0赞 Šimon Tóth 11/12/2012
也许我只是精神失常,但这两个例子(在你答案的末尾)不会失败吗?我的意思是,获得对临时的引用(打赌它或 r 值)不会延长临时生命周期。因此,这应该会导致未定义的行为不打印旧用户。non_mutexconst
0赞 Steve Jessop 11/12/2012
@sbi:我的三个代码片段并不是要相互更正,它们应该是三个类似的代码示例,它们定义的行为根据 .由于您没有使用 C++11 编译器,因此您可以合理地确信最后一个不会出现在您的代码库中,但前两个中的任何一个都可能 :-)mtx
0赞 Steve Jessop 11/12/2012
@Let_Me_Be:“获取对临时的引用不会延长临时生存期”——是的,如果你将引用绑定到临时的,它确实如此。C++03 中的 12.2/5。
0赞 iammilind 11/12/2012 #4

我不觉得你的更改有任何问题。两者都返回不可修改的左值;尝试修改它们会导致 C++03 中的编译器错误。get_name()

如果你想迂腐,你总是可以根据 SFINAE 做出选择,因为你已经模板化了代码。有了它,您可以完全删除 .non_mutex::get_name()

评论

0赞 Steve Jessop 11/12/2012
mutex::get_name不返回右值,而是返回不可修改的左值。
0赞 sbi 11/12/2012
我知道如何修复暴露问题的代码。我只是担心我可能会错过在编译时没有说话但在运行时失败的代码。