HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

Program that replicates itself

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
replicatesprogramitselfthat

Problem

While misreading the beginning of Stage I of this classic paper by Ken Thompson, I decided to create program that replicates itself.

Let's say this program is called Replicator.exe. Upon running it once, it will create a new executable, Replicator_1.exe. Upon running either of those executables, a new executable, Replicator_2.exe, will be created.

Some of the things I am interested in:

  • De-coupling the Windows dependent logic.



  • Naming (I feel like some of my variable and function names are bad).



  • Anything else that seems odd or better ways to do this.



Please note that I wrote this with Visual Studio 2012, so I only have access to a limited amount of C++11 features. For your own safety, please do not try to put the contents of main() into an infinite loop.

This is the main driver:

Driver.cpp

#include "Replicator.h"
#include 
#include 
#include 
#include 

template 
OutIter copy_file (const std::string &filepath, OutIter out, std::ios::openmode open_flags = std::ios::in) ;

int main ()
{
    namespace ff = fun_fs ;

    const std::string filepath = ff::process_path () ;
    const std::string filepath_new = ff::unique_filename (filepath) ;

    std::ofstream file (filepath_new, std::ios::binary) ;
    copy_file (filepath, std::ostream_iterator  (file), std::ios::binary) ;

    return 0 ;
}

template 
OutIter copy_file (const std::string &filepath, OutIter out, std::ios::openmode open_flags)
{
    std::ifstream file ;

    file.open (filepath, open_flags) ;

    if (!file.good ()) {
        return out ;
    }

    file.unsetf (std::ios::skipws) ;

    auto begin = std::istream_iterator  (file) ;
    auto end = std::istream_iterator  () ;

    auto new_out = std::copy (begin, end, out) ;

    return new_out ;
}


These are some helper functions:

Replicator.h

```
#pragma once
#ifndef REPLICATOR_H
#define REPLICATOR_H

#include

namespace fun_fs
{
std::string process_path () ;
std::string unique_filename (std::string filename) ;
}

#end

Solution

De-coupling platform dependent code:

You have very few Windows dependent tasks in your code as it stands.
If I didn't miss anything, the only functions that perform system calls
are process_path() and unique_filename(). I would start of the decoupling by defining a class, instead of loose functions, and adopt a simple inheritance structure to separate the platform specific tasks into a child class.

It would be a lot nicer to define a base class for the portable operations, similar to:

class Replicator 
{
public:

    // This being the only method the client calls.
    void replicateSelf();

    // Might be a default, empty...
    virtual ~Replicator();

protected:

    // These are the platform dependent services, implemented by the specialized class.
    virtual std::string processPath() = 0;
    virtual std::string uniqueFilename(const std::string & filename) = 0;

private:

    // The other helper methods...
};

// A factory function. Could also be a static member function of Replicator.
std::unique_ptr CreateReplicator()
{
#if WINDOWS
    return std::unique_ptr( new WindowsReplicator );
#elif LINUX
    return std::unique_ptr( new LinuxReplicator );
#else
    #error "Missing implementation!"
#endif 
}


And the platform specific class would have to do very little, just implement the two virtual methods:

class WindowsReplicator : public Replicator
{
    std::string processPath()
    {
        // calls GetModuleFileName()
        ...
    }

    std::string uniqueFilename(const std::string & filename) 
    {
        // calls GetFileAttributes()
        ...
    }
};


Client code would use it like this:

int main()
{
    std::unique_ptr replicator = CreateReplicator();
    replicator->replicateSelf();
}


With this setup, you would already achieve a very good separation of the platform
specific parts and the portable parts, plus have your code ready for porting to another system.

Virtual methods, or dynamic dispatch, however, are normally associated with dynamic runtime
behavior. Meaning that virtual methods are very good when you want to switch the underlaying
logic while the program is running. In this case, the platform does not change during runtime.
A Replicator is always a WindowsReplicator or a LinuxReplicator, or whatever.
So you could instead of using virtual methods be using a form of static dispatch.
CRTP comes to mind in this case. I'll leave it to you to further explore the
concept and maybe apply it. Once you create the interface class, changing it to use CRTP is easy.

Side note:

As Loki Astari suggested in a comment, you might very well avoid the dynamic allocation inside CreateReplicator() by making Replicator an implicit singleton. The program will never require more than one instance of the class. So another option for the factory would be something like this:

Replicator & GetReplicatorInstance()
{
#if WINDOWS
    static WindowsReplicator rep;
    return rep;
#elif LINUX
    static LinuxReplicator rep;
    return rep;
#else
    #error "Missing implementation!"
#endif 
}


In C++ 11, initialization order is also thread safe, by the way.

Code Snippets

class Replicator 
{
public:

    // This being the only method the client calls.
    void replicateSelf();

    // Might be a default, empty...
    virtual ~Replicator();

protected:

    // These are the platform dependent services, implemented by the specialized class.
    virtual std::string processPath() = 0;
    virtual std::string uniqueFilename(const std::string & filename) = 0;

private:

    // The other helper methods...
};

// A factory function. Could also be a static member function of Replicator.
std::unique_ptr<Replicator> CreateReplicator()
{
#if WINDOWS
    return std::unique_ptr<Replicator>( new WindowsReplicator );
#elif LINUX
    return std::unique_ptr<Replicator>( new LinuxReplicator );
#else
    #error "Missing implementation!"
#endif 
}
class WindowsReplicator : public Replicator
{
    std::string processPath()
    {
        // calls GetModuleFileName()
        ...
    }

    std::string uniqueFilename(const std::string & filename) 
    {
        // calls GetFileAttributes()
        ...
    }
};
int main()
{
    std::unique_ptr<Replicator> replicator = CreateReplicator();
    replicator->replicateSelf();
}
Replicator & GetReplicatorInstance()
{
#if WINDOWS
    static WindowsReplicator rep;
    return rep;
#elif LINUX
    static LinuxReplicator rep;
    return rep;
#else
    #error "Missing implementation!"
#endif 
}

Context

StackExchange Code Review Q#62691, answer score: 3

Revisions (0)

No revisions yet.