【问题标题】:C++ static variable initialization inside a template function模板函数内的 C++ 静态变量初始化
【发布时间】:2017-05-26 12:30:10
【问题描述】:

我注意到函数模板中静态变量初始化的一种非常奇怪的行为。考虑以下示例:

MyFile * createFile()
{
    std::cout << "createFile" << std::endl;
    return nullptr;
}

template <typename T>
void test(const T& t)
//void test(T t)
{
    static MyFile *f = createFile();
}

void main()
{
    test("one");
    //test("two");
    test("three");
}

只要test 中的f 是静态的,我预计createFile 只会被调用一次。但是,它被调用了两次。

花了一些时间解决这个问题,我注意到从 test 的参数中删除 const 引用可以解决这个问题。另一个有趣的是,传递给函数的字符串长度也会影响初始化:当参数长度相等时,静态变量只初始化一次,否则会发生新的初始化。

有人能解释一下吗?非常欢迎除上述之外的解决方案/解决方法。

【问题讨论】:

  • 您正在使用 2 个不同的 Ts、char[4] ("one") 和 char[6] ("three") 来实例化模板。因此,您有 2 个新类型,都包含 f,因此必须初始化这两个 fs。因此createFile() 被调用了两次。
  • 好的,但是为什么简单的模板版本(没有 const ref)可以工作?
  • 指针衰减(不能按值取数组)。这还不够简单,无法在评论中描述。我会让其他人在答案中描述它。
  • @mentalmushroom:因为它为所有文字创建了相同的 const char * 实例化。在这种情况下,引用是关键部分,它使函数类型的文字部分的长度。

标签: c++ c++11 templates static


【解决方案1】:

文字“一”是const char [4]

这段代码:

test("one")

最好打电话给test(const char (&amp;)[4])

这适用于test(const T&amp;)(因为const char (&amp;) [4] 可以绑定到const char (const&amp;) [4])。

但它不适用于test(T t),因为您不能按值传递字符串文字。它们通过引用传递。

但是,const char[4] 可以衰减为const char*,可以匹配template&lt;class T&gt; void func(T t)

证据在布丁中:

#include <cstdint>
#include <iostream>
#include <typeinfo>

template <typename T, std::size_t N>
void test_const(const T(&t)[N])
{
    std::cout << __func__ << " for literal " << t << " T is a " << typeid(T).name() << " and N is " << N << std::endl;
}

template <typename T>
void test_mutable(T &t)
{
    std::cout << __func__ << " for literal " << t << " T is a " << typeid(T).name() << std::endl;
}

template <typename T>
void test_const_ref(const T &t)
{
    std::cout << __func__ << " for literal " << t << " T is a " << typeid(T).name() << std::endl;
}

template <typename T>
void test_copy(T t)
{
    std::cout << __func__ << " for literal " << t << " T is a " << typeid(T).name() << std::endl;
}

int main()
{
    test_const("one");
    test_const("three");
    test_mutable("one");
    test_mutable("three");
    test_const_ref("one");
    test_const_ref("three");
    test_copy("one");
    test_copy("three");
}

示例结果(clang):

test_const for literal one T is a c and N is 4
test_const for literal three T is a c and N is 6
test_mutable for literal one T is a A4_c
test_mutable for literal three T is a A6_c
test_const_ref for literal one T is a A4_c
test_const_ref for literal three T is a A6_c
test_copy for literal one T is a PKc
test_copy for literal three T is a PKc

这是一个带有解构名称的版本(将在 clang 和 gcc 上编译):

#include <cstdint>
#include <iostream>
#include <typeinfo>
#include <cstdlib>
#include <cxxabi.h>

std::string demangle(const char* name)
{
    int status = -1;
    // enable c++11 by passing the flag -std=c++11 to g++
    std::unique_ptr<char, void(*)(void*)> res {
        abi::__cxa_demangle(name, NULL, NULL, &status),
        std::free
    };

    return (status==0) ? res.get() : name ;
}

template <typename T, std::size_t N>
void test_const(const T(&t)[N])
{
    std::cout << __func__ << " for literal " << t << " T is a " << demangle(typeid(T).name()) << " and N is " << N << std::endl;
}

template <typename T>
void test_mutable(T &t)
{
    std::cout << __func__ << " for literal " << t << " T is a " << demangle(typeid(T).name()) << std::endl;
}

template <typename T>
void test_const_ref(const T &t)
{
    std::cout << __func__ << " for literal " << t << " T is a " << demangle(typeid(T).name()) << std::endl;
}

template <typename T>
void test_copy(T t)
{
    std::cout << __func__ << " for literal " << t << " T is a " << demangle(typeid(T).name()) << std::endl;
}

int main()
{
    test_const("one");
    test_const("three");
    test_mutable("one");
    test_mutable("three");
    test_const_ref("one");
    test_const_ref("three");
    test_copy("one");
    test_copy("three");
}

预期输出:

test_const for literal one T is a char and N is 4
test_const for literal three T is a char and N is 6
test_mutable for literal one T is a char [4]
test_mutable for literal three T is a char [6]
test_const_ref for literal one T is a char [4]
test_const_ref for literal three T is a char [6]
test_copy for literal one T is a char const*
test_copy for literal three T is a char const*

【讨论】:

  • @Quentin 它无法推断出这一点。这将使论点const const char &amp;
  • 糟糕,让我再试一次。它推导出const char[4]T :)
  • @Quentin 前导 const 阻止它。应该是 const const char[4],这是不合法的
  • 显然...It's char[4]。我难住了。 (编辑:const T 的顺序不会改变任何东西)
  • 但是,但是,但是。您认为test(const T&amp;) 将匹配const char * - 因此静态将被构造一次......但test(T&amp;) 将匹配const char[N],因此静态将被构造两次。问题是 OP 的报告正好相反。
【解决方案2】:

作为@RichardHodges 的回答的补充,它解释了为什么使用不同的实例化,很容易只强制一个,因为数组可以通过显式模板实例化衰减为指针:

test<const char *>("one");
test<const char *>("two");
test<const char *>("three");

导致一次调用createFile

事实上(正如 BoBTFish 在评论中所说),这正是你写作时发生的事情:

template <typename T>
void test(const T t)

无论数组大小如何,数组都会自动衰减为const char *,因为 C++ 不允许直接分配数组。

顺便说一句,void main() 不好。始终使用 int main() 并明确返回。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-21
    • 1970-01-01
    • 2013-07-11
    • 1970-01-01
    相关资源
    最近更新 更多