当前位置:  开发笔记 > 编程语言 > 正文

C++构造替换C构造

如何解决《C++构造替换C构造》经验,为你挑选了9个好方法。

在与我的团队中新来的开发人员讨论之后,我意识到在C++中仍然存在使用C构造的习惯,因为它们应该更好(即更快,更精简,更漂亮,选择你的理由).

与类似的C++构造相比,有哪些示例值得共享,显示C构造?

对于每个示例,我需要阅读C++构造与原始C构造一样好或甚至更好的原因.目的是提供一些在C++代码中被认为有些危险/不安全的C构造的替代方案(C++ 0x只有在只有明确标记为C++ 0x的情况下才能接受答案).

我将在答案(结构内联初始化)下面作为示例发布.

注1:请每个案例一个答案.如果您有多个案例,请发布多个答案

注2:这不是C问题.不要在此问题中添加"C"标签. 这不应该成为C++和C之间的斗争.只研究C++的C子集的一些结构,以及它们在其他C++"工具包"中的替代方案.

注3:这不是一个抨击C的问题.我想要理由.吹嘘,抨击和未经证实的比较将被下调.提及没有C等价物的C++特性可以被认为是不可能的主题:我希望并排放置一个针对C++特性的C特征.



1> 小智..:

RAII以及所有随后的荣耀与手动资源获取/发布

在C:

Resource r;
r = Acquire(...);

... Code that uses r ...

Release(r);

例如,Resource可以是指向内存的指针,Acquire/Release将分配/释放该内存,或者它可以是一个打开的文件描述符,其中Acquire/Release将打开/关闭该文件.

这提出了许多问题:

    你可能忘记打电话了 Release

    代码不传达有关数据流的信息r.如果r在同一范围内获取和发布,则代码不会自行记录.

    在之间的时间Resource rr.Acquire(...),r实际上是访问,尽管被初始化.这是错误的来源.

应用RAII(资源获取是初始化)方法,我们在C++中获得

class ResourceRAII
{
  Resource rawResource;

  public:
  ResourceRAII(...) {rawResource = Acquire(...);}
  ~ResourceRAII() {Release(rawResource);}

  // Functions for manipulating the resource
};

...

{
  ResourceRAII r(...);

  ... Code that uses r ...
}

C++版本将确保您不会忘记释放资源(如果这样做,您会发生内存泄漏,调试工具更容易检测到).它迫使程序员明确说明资源的数据流是如何流动的(即:如果它只存在于函数的范围内,这将通过堆栈上的ResourceRAII构造来表明).在创建资源对象和资源无效的销毁之间没有任何意义.

它也是例外安全!



2> JohnMcG..:

宏与内联模板

C风格:

#define max(x,y) (x) > (y) ? (x) : (y)

C++风格

inline template
const T& max(const T& x, const T& y)
{
   return x > y ? x : y;
}

更喜欢C++方法的理由:

类型安全 - 强制参数必须是相同类型

max定义中的语法错误将指向正确的位置,而不是您调用宏的位置

可以调试到函数中


Max真的不是那么简单,你真的应该读这个http://www.ddj.com/cpp/184403774

3> Cat Plus Plu..:

动态数组与STL容器

C-风格:

int **foo = new int*[n];
for (int x = 0; x < n; ++x) foo[x] = new int[m];
// (...)
for (int x = 0; x < n; ++x) delete[] foo[x];
delete[] foo;

C++ - 风格:

std::vector< std::vector > foo(n, std::vector(m));
// (...)

为什么STL容器更好:

它们是可调整大小的,阵列具有固定的大小

它们是异常安全的 - 如果在(...)部分发生未处理的异常,那么数组内存可能会泄漏 - 容器在堆栈上创建,因此在展开期间它将被正确销毁

它们实现了绑定检查,例如vector :: at()(越过数组的边界很可能会产生访问冲突并终止程序)

它们更容易使用,例如vector :: clear()与手动清除数组

它们隐藏了内存管理细节,使代码更具可读性



4> Onorio Caten..:

#define vs. const

我一直看到这样的代码来自长期编码C的开发人员:

#define MYBUFSIZE 256

.  .  . 

char somestring[MYBUFSIZE];

等等

在C++中,这会更好:

const int MYBUFSIZE = 256;

char somestring[MYBUFSIZE];

当然,更好的是开发人员使用std :: string而不是char数组,但这是一个单独的问题.

C宏的问题很多 - 在这种情况下,类型检查不是主要问题.

从我所看到的情况来看,对于C程序员转换为C++而言,这似乎是一个非常难的习惯.



5> Steve..:

默认参数:

C:

void AddUser(LPCSTR lpcstrName, int iAge, const char *lpcstrAddress);
void AddUserByNameOnly(LPCSTR lpcstrName)
  {
  AddUser(lpcstrName, -1,NULL);
  }

C++替换/等效:

void User::Add(LPCSTR lpcstrName, int iAge=-1, const char *lpcstrAddress=NULL);

为什么这是一个改进:

允许程序员用更少的源代码行以更紧凑的形式表达程序的功能.还允许将未使用参数的默认值表示为最接近实际使用位置的值.对于调用者,简化了类/结构的接口.



6> Konrad Rudol..:

C的qsort功能与C++的sort功能模板.后者通过模板提供类型安全,模板具有明显且不太明显的后果:

类型安全性使代码不易出错.

界面sort稍微容易一些(不需要指定元素的大小).

编译器知道比较器函数的类型.如果,代替函数指针,用户经过一个功能对象,sort将执行更快的qsort,因为内联的比较变得微不足道.对于C版本中必需的函数指针,情况并非如此.

下面的例子说明的使用qsortsort的C风格阵列上int.

int pint_less_than(void const* pa, void const* pb) {
    return *static_cast(pa) - *static_cast(pb);
}

struct greater_than {
    bool operator ()(int a, int b) {
        return a > b;
    }
};

template 
void print(int (&arr)[Size]) {
    std::copy(arr, arr + Size, std::ostream_iterator(std::cout, " "));
    std::cout << std::endl;
}

int main() {
    std::size_t const size = 5;
    int values[] = { 4, 3, 6, 8, 2 };

    { // qsort
        int arr[size];
        std::copy(values, values + size, arr);
        std::qsort(arr, size, sizeof(int), &pint_less_than);
        print(arr);
    }

    { // sort
        int arr[size];
        std::copy(values, values + size, arr);
        std::sort(arr, arr + size);
        print(arr);
    }

    { // sort with custom comparer
        int arr[size];
        std::copy(values, values + size, arr);
        std::sort(arr, arr + size, greater_than());
        print(arr);
    }
}



7> paercebal..:

struct inline初始化与内联构造函数

有时,我们需要在C++中简单地聚合数据.数据有些独立,通过封装来保护它不值得付出努力.

// C-like code in C++
struct CRect
{
   int x ;
   int y ;
} ;

void doSomething()
{
   CRect r0 ;               // uninitialized
   CRect r1 = { 25, 40 } ;  // vulnerable to some silent struct reordering,
                            // or adding a parameter
}

; 我看到上面的代码有三个问题:

如果对象没有专门初始化,则不会全部初始化

如果我们改变x或y(无论出于何种原因),doSomething()中的默认C初始化现在将是错误的

如果我们添加az成员,并且默认情况下喜欢它为"零",我们仍然需要更改每个内联初始化

下面的代码将内联构造函数(如果真的很有用),因此将具有零成本(如上面的C代码):

// C++
struct CRect
{
   CRect() : x(0), y(0) {} ;
   CRect(int X, int Y) : x(X), y(Y) {} ;
   int x ;
   int y ;
} ;

void doSomething()
{
   CRect r0 ;
   CRect r1(25, 40) ;
}

(奖金是我们可以添加一个运算符==方法,但这个奖金超出了主题,所以值得一提,但不值得作为答案.)

编辑:C99已命名已初始化

亚当罗森菲尔德做了一个有趣的评论,我觉得很有意思:

C99允许命名的初始值设定项: CRect r = {.x = 25,.y = 40}

这不会在C++中编译.我想这应该添加到C++中,如果仅用于C兼容性.无论如何,在C中,它缓解了这个答案中提到的问题.



8> jeffythedrag..:

iostream vs stdio.h

在C:

#include 

int main()
{
    int num = 42;

    printf("%s%d%c", "Hello World\n", num, '\n');

    return 0;
}

格式字符串在运行时解析,这意味着它不是类型安全的.

在C++中:

#include 

int main()
{
    int num = 42;

    std::cout << "Hello World\n" << num << '\n';
}

数据类型在编译时是已知的,因为不需要格式字符串,所以也可以输入更少的数据类型.



9> paercebal..:

继fizzer在C++构建的帖子取代C构造之后,我将在这里写下我的答案:

警告:下面提出的C++解决方案不是标准的C++,而是g ++和Visual C++的扩展,并且被提议作为C++ 0x的标准(感谢Fizzer对此的评论)

请注意,Johannes Schaub - litb的答案提供了另一种符合C++ 03标准的方法.

如何提取C数组的大小?

建议的C解决方案

来源:C++宏什么时候有用?


#define ARRAY_SIZE(arr) (sizeof arr / sizeof arr[0])

与当前线程中讨论的"首选"模板解决方案不同,您可以将其用作常量表达式:

char src[23];
int dest[ARRAY_SIZE(src)];

我不同意Fizzer,因为有一个模板化的解决方案能够生成一个常量表达式(事实上,模板的一个非常有趣的部分是它们在编译时生成常量表达式的能力)

无论如何,ARRAY_SIZE是一个能够提取C数组大小的宏.我不会详细说明C++中的宏:目的是找到一个相同或更好的C++解决方案.

一个更好的C++解决方案?

以下C++版本没有任何宏问题,并且可以以相同的方式执行任何操作:

template 
inline size_t array_size(T (&p)[size])
{
   // return sizeof(p)/sizeof(p[0]) ;
   return size ; // corrected after Konrad Rudolph's comment.
}

示范

如以下代码所示:

#include 

// C-like macro
#define ARRAY_SIZE(arr) (sizeof arr / sizeof arr[0])

// C++ replacement
template 
inline size_t array_size(T (&p)[size])
{
   // return sizeof(p)/sizeof(p[0]) ;
   return size ; // corrected after Konrad Rudolph's comment.
}

int main(int argc, char **argv)
{
   char src[23];
   char * src2 = new char[23] ;
   int dest[ARRAY_SIZE(src)];
   int dest2[array_size(src)];

   std::cout << "ARRAY_SIZE(src)  : " << ARRAY_SIZE(src) << std::endl ;
   std::cout << "array_size(src)  : " << array_size(src) << std::endl ;
   std::cout << "ARRAY_SIZE(src2) : " << ARRAY_SIZE(src2) << std::endl ;
   // The next line won't compile
   //std::cout << "array_size(src2) : " << array_size(src2) << std::endl ;

   return 0;
}

这将输出:

ARRAY_SIZE(src)  : 23
array_size(src)  : 23
ARRAY_SIZE(src2) : 4

在上面的代码中,宏错误地指向了一个数组的指针,因此返回了一个错误的值(4,而不是23).相反,模板拒绝编译:

/main.cpp|539|error: no matching function for call to ‘array_size(char*&)’|

从而证明模板解决方案是:*能够在编译时生成常量表达式*如果以错误的方式使用,则能够停止编译

结论

因此,总而言之,模板的参数是:

代码没有像宏观一样的污染

可以隐藏在命名空间内

可以防止错误的类型评估(指向内存的指针不是数组)

注意:感谢Microsoft实现strcpy_s for C++ ...我知道有一天我会为我服务... ^ _ ^

http://msdn.microsoft.com/en-us/library/td1esda9.aspx

编辑:解决方案是针对C++ 0x标准化的扩展

Fizzer确实正确评论这在当前的C++标准中是无效的,并且非常正确(因为我可以在g ++上验证-pedantic选项已经检查).

尽管如此,今天不仅可以在两个主要的编译器(即Visual C++和g ++)上使用它,但这被考虑用于C++ 0x,如以下草案中所提出的:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2003/n1521.pdf(参见"2.1常量表达式函数"部分)

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2691.pdf(参见"5.19常量表达式"和"7.1.5 constexpr说明符"部分)

C++ 0x的唯一变化可能是:

inline template 
constexpr size_t array_size(T (&p)[size])
{
   //return sizeof(p)/sizeof(p[0]) ;
   return size ; // corrected after Konrad Rudolph's comment.
}

(注意constexpr关键字)

编辑2

Johannes Schaub - litb的回答提供了另一种符合C++ 03标准的方法.我将复制粘贴源文件以供参考,但请访问他的答案以获得完整的示例(并upmod它!):

template char (& array_size(T(&)[N]) )[N];

用作:

int p[] = { 1, 2, 3, 4, 5, 6 };
int u[sizeof array_size(p)]; // we get the size (6) at compile time.

我脑中的许多神经元都被炒得让我理解了array_size它的本质(提示:它是一个返回对N个字符数组的引用的函数).

:-)

推荐阅读
Life一切安好
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有