6
\$\begingroup\$

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:

  1. De-coupling the Windows dependent logic.
  2. Naming (I feel like some of my variable and function names are bad).
  3. 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 <algorithm>
#include <fstream>
#include <iterator>
#include <string>

template <class OutIter>
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 <char> (file), std::ios::binary) ;

    return 0 ;
}

template <class OutIter>
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 <char> (file) ;
    auto end = std::istream_iterator <char> () ;

    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 <string>

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

#endif

Replicator.cpp

#include "Replicator.h"

#include <string>
#include <system_error>
#include <utility>

#include <Windows.h>

namespace fun_fs
{
    static std::pair <std::string, std::string> split_file_extension (const std::string &filename) ;
    static std::string increment_count (const std::string &filename) ;  
}

std::string fun_fs::process_path ()
{
    std::string path (500, ' ') ;
    DWORD dw = ::GetModuleFileName (nullptr, &path[0], path.size ()) ;

    // Do not mistake hidden files for file extensions.
    if (dw == 0 || dw == path.size ()) {
        std::error_code ec (::GetLastError (), std::system_category ()) ;
        throw std::system_error (ec, "::GetModuleFileName () failed.") ;
    }

    path.resize (dw) ;

    return path ;
}

std::string fun_fs::unique_filename (std::string filename)
{
    auto file_and_extension = split_file_extension (filename) ;

    std::string filename_part = std::move (file_and_extension.first) ;
    const std::string file_extension = std::move (file_and_extension.second) ;

    do {
        filename_part = increment_count (filename_part) ;
        filename = filename_part + file_extension ;
    } while (::GetFileAttributes (filename.data ()) != INVALID_FILE_ATTRIBUTES) ;

    return filename ;
}

// example: "name.txt" -> {"name", ".txt"}
// example: "name" -> {"name", ""}
static std::pair <std::string, std::string> fun_fs::split_file_extension (const std::string &filename)
{
    std::string file_extension ;

    auto index = filename.rfind ('.') ;

    // ignore hidden files
    if (index != 0 && index != std::string::npos) {
        return std::make_pair (filename.substr (0, index), filename.substr (index)) ;
    }

    return std::make_pair (filename, "") ;
}

// example: "name" -> "name_1"
// example: "name_2" -> "name_3"
// example: "name_2cool" -> "name_2cool_1"
static std::string fun_fs::increment_count (const std::string &filename)
{
    const std::string start_count = "1" ;

    auto index = filename.rfind ('_') ;

    if (index == (filename.size () - 1)) {
        return filename + start_count ;
    }

    else if (index != std::string::npos) {
        const std::string possible_number = filename.substr (index + 1) ;
        std::size_t end_of_conversion = 0 ;

        try {
            int number = std::stoi (possible_number, &end_of_conversion) ;
            if (end_of_conversion == possible_number.size ()) {
                return filename.substr (0, index + 1) + std::to_string (number + 1) ;
            }
        }

        catch (std::invalid_argument) {
            // do nothing...
        }
    } 


    return filename + "_" + start_count ;
}
\$\endgroup\$
  • 2
    \$\begingroup\$ In the same family but much more interesting is to write a Quine \$\endgroup\$ – Martin York Sep 12 '14 at 0:13
  • \$\begingroup\$ @LokiAstari Wouldn't that be trivial? \$\endgroup\$ – jliv902 Sep 12 '14 at 15:41
  • \$\begingroup\$ You should try it. Its harder than you think. Diffing the output against the original source should find no difference and remember it takes no input so you can read the source. \$\endgroup\$ – Martin York Sep 12 '14 at 15:58
  • \$\begingroup\$ @LokiAstari Did you see my link? Am I misunderstanding the requirements? \$\endgroup\$ – jliv902 Sep 12 '14 at 15:59
  • 1
    \$\begingroup\$ As a side note: Copy a file in an sane, safe and efficient way \$\endgroup\$ – Martin York Sep 12 '14 at 16:02
3
\$\begingroup\$

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<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 
}

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> 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.

|improve this answer|||||
\$\endgroup\$
  • 1
    \$\begingroup\$ Why dynamically create the replicator? It has no state. So the factory could just have one and return a reference. \$\endgroup\$ – Martin York Sep 12 '14 at 16:43
  • \$\begingroup\$ Yes, it is true. It is a singleton. \$\endgroup\$ – glampert Sep 12 '14 at 17:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.