什么是C ++中的可变函数的一个好的类型安全的替代? [英] What is a good typesafe alternative to variadic functions in C++?

查看:108
本文介绍了什么是C ++中的可变函数的一个好的类型安全的替代?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

与此问题联合使用。我有麻烦提出一个良好的类型安全的解决方案,以下看似基本的问题。我有一个类music_playlist,它有一个应该播放的歌曲的列表。看起来很简单的权利,只是做一个std ::列表中的所有歌曲,并使其可供用户。然而,出于必要性,音频解码和音频渲染发生在单独的线程上。所以列表需要互斥保护。通常情况下,互斥体被其他程序员使用我的库忘记了。这显然导致了奇怪的问题。

In joint with this question. I am having trouble coming up with a good type safe solution to the following seemingly basic problem. I have a class music_playlist that has a list of the songs it should play. Seems pretty simple right, just make an std::list of all the songs in queue, and make it available to the user. However, out of necessity the audio decoding, and the audio rendering happens on separate threads. So the list needs to be mutex protected. Often times the mutex was forgotten by other programmers using my library. Which obviously led to "strange" problems.

所以一开始我只是为类写了setters。

So at first I just wrote setters for the class.

struct music{};
class music_playlist{
private:
     std::list<music> playlist;
public:
     void add_song(music &song){playlist.push_back(song);}
     void clear(){playlist.clear();}
     void set_list(std::list<music> &songs){playlist.assign(songs.begin(),songs.end());}
//etc
};

这导致了如下的用户代码...

This led to user code like below...

music song1;
music song2;
music song3;
music song4;
music song5;
music song6;
music_playlist playlist1;
playlist1.add_song(song1);
playlist1.add_song(song2);
playlist1.add_song(song3);
playlist1.add_song(song4);
playlist1.add_song(song5);
playlist1.add_song(song6);

//or
music_playlist playlist2;
std::list<music> songs;
songs.push_back(song1);
songs.push_back(song2);
songs.push_back(song3);
songs.push_back(song3);
songs.push_back(song5);
songs.push_back(song6);
playlist2.set_list(songs);

这个工作非常明确。这是非常繁琐的输出,它是错误容易,因为所有的实际工作周围的烦恼。为了演示这一点,我实际上故意在上面的代码,这样的东西很容易做,可能会通过代码审查不变,而song4永远不会播放在播放列表2。

While this works and is very explicit. It is very tedious to type out and it is bug prone due to all the cruft around the actual work. To demonstrate this, I actually intentionally put a bug into the above code, something like this would be easy to make and would probably go through code reviews untouched, while song4 never plays in playlist 2.

从那里我去研究可变函数。

From there I went to look into variadic functions.

struct music{};
class music_playlist{
private:
     std::list<music> playlist;
public:
     void set_listA(music *first,...){
     //Not guaranteed to work, but usually does... bleh
         va_list Arguments;
    va_start(Arguments, first);
    if (first) {
        playlist.push_back(first);
    }
    while (first) {
        music * a = va_arg(Arguments, music*);
        if (a) {
            playlist.push_back(a);
        }else {
            break;
        }
    }
    }
    void set_listB(int count,music first,...){
         va_list Arguments;
     va_start(Arguments, first);
     playlist.push_back(first);

    while (--count) {
        music a = va_arg(Arguments, music);
            playlist.push_back(a);
    }
    }
//etc
}; 

这将导致用户代码如下:

Which would lead to a users code like below...

playlist1.set_listA(&song1,&song2,&song3,&song4,&song5,&song6,NULL);
//playlist1.set_listA(&song1,&song2,&song3,&song4,&song5,&song6); runtime error!!
//or
playlist2.set_listB(6,song1,song2,song3,song4,song5,song6);

现在,它更容易看到一首歌曲是否添加了两次或不包括。然而在解决方案A它将崩溃,如果NULL不在列表的结尾,并且不是跨平台。在solutionB中,你必须计算可能导致几个错误的参数的数量。此外,没有一个解决方案是类型安全的,用户可以传入一个无关的类型,并等待崩溃在运行时。这似乎不是一个可持续的解决方案。所以我遇到了std :: initializer_list。不能使用它开发的几个编译器没有支持它。所以我试图效仿它。我结束了这个解决方案如下。

Now its much easier to see if a song was added twice or not included. However in solution A it will crash if NULL is not at the end of the list, and is not cross platform. In solutionB you have to count the amount of arguments which can lead to several bugs. Also, none of the solutions are type safe, and the user could pass in an unrelated type and wait for the crash at runtime. This didn't seem to be a sustainable solution. So then I came across std::initializer_list. Can't use it several of the compilers I develop for don't have support for it yet. So I tried to emulate it. I ended up with this solution below.

这是对,的使用,

这将导致用户代码看起来像这样...

Which would lead to a users code looking like this...

struct music_playlist{
    list<music> queue;
//...
};
int main (int argc, const char * argv[])
{
    music_playlist playlist;
    music song1;
    music song2;
    music song3;
    music song4;
    playlist.queue = song1,song2; // The queue now contains song1 and song2
    playlist.queue+= song1,song3,song4; //The queue now contains two song1s and song2-4
    playlist.queue = song2; //the queue now only contains song2
    return 0;
}

此语法不会对我们的小测试组造成混淆。然而,我严重关注滥用操作符过载这么严重。所以我发布了上面的问题。我想看看什么程序员是我们测试组认为比较专家的。相当多的程序员不喜欢它,但它似乎比上述解决方案更好,因为它会在编译时捕获大多数错误,而不是只在运行时。但是Tom发布了一个有趣的计数器示例,这将导致意外的行为。

This syntax was not confusing to our small testgroup. However, I had serious concerns with abusing operator overloading so severely. So I posted the question above. I wanted to see what programmers that were comparatively experts to our test group thought. Quite alot of programmers did not like it however it seemed better than the above solutions because it would catch most bugs at compile time instead of just at run time. However Tom posted an interesting counter example, that would cause unexpected behavior.

//lets combine differnt playlists
new_playlist.queue =    song1        //the first playlist
                      ,(song3,song4) //the second playlist //opps, I didn't add song 3!
                      , song5;

这让我觉得这个解决方案。你有什么想法更好的解决方案?

This sours me to that solution. So do you have any ideas about a better solution?

推荐答案

要记住的第一个问题是:这是一个问题,是否这是一个值得解决的问题。由于您正在创建一个界面,因此您的客户端将是用户代码(不是人员,代码)。您的客户硬编码在代码中的播放列表有多少次,存储从文件加载它们或从他们的用户选择构造它们?

The first question that comes to mind is whether this is a problem, and whether this is a problem worth the solution. Since you are creating an interface, your client will be user code (not people, code). How many times will your clients hardcode playlists in the code versus say, store load them from a file or construct them from their user selections?

考虑功能的实际价值,并将其与它们如何使用您的库的影响进行比较,并考虑您的用户可能对更改的界面有多少问题。

Think on the actual value of the feature and compare that with the impact that it will have in how they use your library, and think on how many problems your users might have with the changed interface.

最简单的解决方案是接受迭代器来构造/重置列表,然后让用户处理这个问题。当然,他们可以以你已经展示的方式构建他们自己的容器,但他们也可以使用 Boost.Assignment 来处理锅炉板,因此他们的用户代码将如下所示:

The simplest solution is accepting iterators to construct/reset your list, and then let the users deal with the problem. Of course they can build their own container in the way that you have shown, but they can also use Boost.Assignment to take care of the boiler plate, so their user code will look like:

std::vector<music> songs = boost::assign::list_of()( song1 )( song2 );
play_list.set( songs.begin(), songs.end() );

或者如果他们对这个库不舒服,可以使用一个普通的旧数组:

Or if they are not comfortable with that library the can use a plain old array:

music songs[2] = { song1, song2 };
play_list:set( songs, songs + 2 ); // add your preferred magic to calculate the size

这篇关于什么是C ++中的可变函数的一个好的类型安全的替代?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆